Thursday, January 13, 2011

Creating A "Move" Command With .NET API

In AutoCAD VBA era, although programmatic moving an entity with user interaction is fairly easy to do, but there is no way to make it behave like AutoCAD built-in "MOVE" command - the entity to be moved can be dragged with the cursor, which gives user a very good visual hint as where the entity is moving.

With AutoCAD .NET API being available, programming an custom command that allows user to drag entity or entities becomes a hot AutoCAD programming technique that every AutoCAD programmer would like to try out.

There are two ways to do entity dragging: creating a custom Jig class that derived from EntityJig or DrawJig class; or use a delegate (DragCallBack) in Editor.Drag() method. There are quite some discussions on the topic of Jig in various blogs/forums. In this article, I focus on the latter - Editor.Drag() with a delegate.

Kean Walmsly posted an article on this topic here. However, in dragging part of his code, there is no code to show a rubber band and show the entity/entities being dragged in highlight (of course this is because that was not what his post focused on).

The code shown below tries to imitate AutoCAD's "MOVE" command: the entity/entities can be selected before or after the custom command starts; the selected entity/entities being highlighted, a rubber band line being drawn from base point to the mouse cursor; the dragging method can ask user to pick base point; if no base point is required, the code finds the lower left corner of bounding box of all selected entities as base point... Enough explanations. Here are the code snippets following.

First, in the project, add a class "DragMove":

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

namespace InteractiveDrag
{
    public class DragMove
    {
        private Document _dwg;
        private SelectionSet _sset;

        private Point3d _basePoint = new Point3d(0.0, 0.0, 0.0);
        private bool _useBasePoint = false;
        private Line _rubberLine = null;

        public DragMove(Document dwg, SelectionSet ss)
        {
            _dwg = dwg;
            _sset = ss;
        }

        #region public properties

        public bool UseBasePoint
        {
            set { _useBasePoint = value; }
            get { return _useBasePoint; }
        }

        public Point3d BasePoint
        {
            set { _basePoint = value; }
            get { return _basePoint; }
        }

        #endregion

        #region public methods

        public void DoDrag()
        {
            if (_sset.Count == 0) return;

            _rubberLine = null;

            using (Transaction tran = 
                _dwg.Database.TransactionManager.StartTransaction())
            {
                //Highlight entities
                SetHighlight(true, tran);

                if (!_useBasePoint)
                {
                    _basePoint = GetDefaultBasePoint();
                }
                else
                {
                    Point3d pt;
                    if (!PickBasePoint(_dwg.Editor, out pt)) return;

                    _basePoint = pt;
                    _useBasePoint = true;
                }

                PromptPointResult ptRes = _dwg.Editor.Drag
                    (_sset, "\nPick point to move to: ",
                        delegate(Point3d pt, ref Matrix3d mat)
                        {
                            if (pt == _basePoint)
                            {
                                return SamplerStatus.NoChange;
                            }
                            else
                            {
                                if (_useBasePoint)
                                {
                                    if (_rubberLine == null)
                                    {
                                        _rubberLine = new Line(_basePoint, pt);
                                        _rubberLine.SetDatabaseDefaults(_dwg.Database);

                                        IntegerCollection intCol;

                                        //Create transient graphics: rubberband line
                                        intCol = new IntegerCollection();
                                        TransientManager.CurrentTransientManager.
                                            AddTransient(_rubberLine, 
                                            TransientDrawingMode.DirectShortTerm, 128, intCol);
                                    }
                                    else
                                    {
                                        _rubberLine.EndPoint = pt;

                                        //Update the transient graphics
                                        IntegerCollection intCol = new IntegerCollection();
                                        TransientManager.CurrentTransientManager.
                                            UpdateTransient(_rubberLine, intCol);
                                    }
                                }

                                mat = Matrix3d.Displacement(_basePoint.GetVectorTo(pt));
                                return SamplerStatus.OK;
                            }
                        }
                    );

                if (_rubberLine != null)
                {
                    //Clear transient graphics
                    IntegerCollection intCol = new IntegerCollection(); 
                    TransientManager.CurrentTransientManager.
                        EraseTransient(_rubberLine, intCol);

                    _rubberLine.Dispose();
                    _rubberLine = null;
                }

                if (ptRes.Status == PromptStatus.OK)
                {
                    MoveObjects(ptRes.Value, tran);
                }

                //Unhighlight entities
                SetHighlight(false, tran);

                tran.Commit();
            }
        }

        public void DoDrag(bool pickBasePt)
        {
            _useBasePoint = pickBasePt;

            DoDrag();
        }

        public void DoDrag(Point3d basePt)
        {
            _basePoint = basePt;
            _useBasePoint = true;

            DoDrag();
        }

        #endregion

        #region private methods

        private Point3d GetDefaultBasePoint()
        {
            Point3d pt = new Point3d();

            Extents3d exts = new Extents3d(
                new Point3d(0.0, 0.0, 0.0), new Point3d(0.0, 0.0, 0.0));

            using (Transaction tran = 
                _dwg.Database.TransactionManager.StartTransaction())
            {
                ObjectId[] ids=_sset.GetObjectIds();
                for (int i = 0; i < ids.Length; i++ )
                {
                    ObjectId id = ids[i];
                    Entity ent = (Entity)tran.GetObject(id, OpenMode.ForRead);

                    if (i == 0)
                    {
                        exts = ent.GeometricExtents;
                    }
                    else
                    {
                        Extents3d ext = ent.GeometricExtents;
                        exts.AddExtents(ext);
                    }
                }

                tran.Commit();
            }

            return exts.MinPoint;
        }

        private bool PickBasePoint(Editor ed, out Point3d pt)
        {
            pt = new Point3d();

            PromptPointOptions opt = new PromptPointOptions("Pick base point: ");
            PromptPointResult res = ed.GetPoint(opt);
            if (res.Status == PromptStatus.OK)
            {
                pt = res.Value;
                return true;
            }
            else
            {
                return false;
            }
        }

        private void MoveObjects(Point3d pt, Transaction tran)
        {
            Matrix3d mat = Matrix3d.Displacement(_basePoint.GetVectorTo(pt));
            foreach (ObjectId id in _sset.GetObjectIds())
            {
                Entity ent = (Entity)tran.GetObject(id, OpenMode.ForWrite);
                ent.TransformBy(mat);
            }
        }

        private void SetHighlight(bool highlight, Transaction tran)
        {
            foreach (ObjectId id in _sset.GetObjectIds())
            {
                Entity ent = (Entity)tran.GetObject(id, OpenMode.ForWrite);

                if (highlight)
                    ent.Highlight();
                else
                    ent.Unhighlight();
            }
        }

        #endregion
    }
}

Here is the Command class:

using System.Collections.Generic;

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

[assembly: CommandClass(typeof(InteractiveDrag.MyCommand))]

namespace InteractiveDrag
{
    public class MyCommand
    {
        [CommandMethod("DragMove", CommandFlags.UsePickSet)]
        public static void DoMove()
        {
            Document dwg = Autodesk.AutoCAD.ApplicationServices.
                Application.DocumentManager.MdiActiveDocument;

            Editor ed = dwg.Editor;

            //Get PickFirst SelectionSet
            PromptSelectionResult setRes = ed.SelectImplied();

            if (setRes.Status != PromptStatus.OK)
            {
                //if not PickFirst set, ask user to pick:
                ObjectId[] ids = GetUserPickedObjects(dwg);
                if (ids.Length == 0)
                {
                    ed.WriteMessage("\n*Cancelled*");
                    ed.WriteMessage("\n*Cancelled*");
                    return;
                }

                ed.SetImpliedSelection(ids);
                setRes = ed.SelectImplied();

                if (setRes.Status != PromptStatus.OK)
                {
                    ed.Regen();
                    return;
                }
            }
            
            //Do the dragging with the selectionSet
            DragMove drag = new DragMove(dwg, setRes.Value);
            drag.DoDrag(true);
        }

        private static ObjectId[] GetUserPickedObjects(Document dwg)
        {
            List ids = new List();
            using (Transaction tran = 
                dwg.Database.TransactionManager.StartTransaction())
            {
                bool go = true;
                while (go)
                {
                    go = false;

                    PromptEntityOptions opt = 
                        new PromptEntityOptions("\nPick an entity: ");
                    PromptEntityResult res = dwg.Editor.GetEntity(opt);

                    if (res.Status == PromptStatus.OK)
                    {
                        bool exists = false;
                        foreach (ObjectId id in ids)
                        {
                            if (id == res.ObjectId)
                            {
                                exists = true;
                                break;
                            }
                        }

                        if (!exists)
                        {
                            //Highlight
                            Entity ent = (Entity)tran.GetObject(
                                res.ObjectId, OpenMode.ForWrite);

                            ent.Highlight();

                            ids.Add(res.ObjectId);
                            go = true;
                        }
                    }
                }

                tran.Commit();
            }

            return ids.ToArray();
        }
    }
}

Build the project and "NETLOAD" it into AutoCAD. See this video clip for the behaviuor of the code shown above.

With some slight modification to the code one can easily do a drag-copying, drag-rotating...

7 comments:

Jeroen Verdonschot said...

Hi Norman,

Great blog!

I used your code and made a command that moves a selectionset.
(had a handler error but it seemed that I had to make a copy of the set that is passed to the drag()).

But now I still have a little thing. Ortho doesn't work.
I can't get it to work. Do you have a suggestion?

I also saw your posts with the transientmanager.
Isn't there a way to get the editor.getpoint() to work with a transient that moves the selectionset.
The options (rubberline, ortho, directinput, etc) of the getpoint() are working great. I have found a good example of a code that works as well as Getpoint().

Maybe I'm asking something ridiculous (forgive me I'm a newbie) but it could be a great way to recreate the move command and have more freedom to extras to the command. The transient-getpoint combination could be great to move very large selectionsets.

Thanks,

Jeroen

Anonymous said...

Hi Jeroen,

Yes, it seems ORTHO mode cannot be turned on in Editor.Drag().

And yes, as you noticed, in one of my old post (http://drive-cad-with-code.blogspot.com/2009/07/help-autocad-users-visually-with_24.html, 2009-07), I coded an example of jig, using Transient Graphics. It was almost a custom "MOVE" command. I mean, it can be used as a base to create a complete custom "MOVE" command. I have verified that ORTHO mode can be turned on/off during mouse move when calling Editor.GetPoint(). So, if doing "MOVE" command like that, the ORTHO would not be an issue.

I may be able to give it a try.

Norman Yuan

Jeroen Verdonschot said...

Hi Norman,

That would be great. I used the move command to learn VB.net and VB.net for Autocad at the same time. I had no experience so I thought I recreate the move command to learn vb.net.
But it seems to be more complex (and interessting) than I thought.

If you could look into it it would be great. The different parts of this command can be used for all kinds of commands so should be very useful.

Thanks,

Jeroen

Norman Yuan said...

Jeroen,

I just posted another version of "MOVE" command, which takes care of "ORTHO" mode being on or off, by handling PointMonitor event, instead of using Editor.Drag().

Unknown said...

what a nice coding....
Six Month industrial Training in Chandigarh

intercad said...

There's usually some tutorials within the program that might help you get started.

Solidworks Training

Anonymous said...

Thanks for the code.

I do have a comment if you will be so kind as to permit me - anonymous delegate functions are difficult to read - the code would perhaps be more easily understand if a more readable solution was provided.

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.