Author Topic: Transviewport Selection!  (Read 1956 times)

0 Members and 1 Guest are viewing this topic.

zoltan

  • Newt
  • Posts: 188
Transviewport Selection!
« on: March 15, 2011, 08:55:53 PM »
Ok, Transviewport is not really a word; I just made it up!

I wanted to throw this out there to see if any of you ambitious folks would have fun expanding upon it.

Here I have written a set of classes for doing a Nested Entity Selection through a viewport while in paperspace.  The GetNestedEntityThroughViewport extension method of the Editor object works like the normal GetNestedEnitiy method, except it will return an object inside of a viewport while the user is in paperspace.  If you use it while in modelspace, it will act like the normal Editor.GetNestedEnitiy() call. If you use it in paperspace and select an object in paperspace, it will also act like a normal Editor.GetNestedEnitiy() call.  But, if you use it in paperspace and click on an entity in modelspace that is visible through a viewport, it will translate your pick through the view and pick the object in modelspace.

I ended up compromising a few things to get it to work and my original idea for it was more ambitious.

First, I ended up having to use a DrawJig to make a fake object selection prompt, because I couldn't get the Editor.GetNestedEntity() call to return if nothing is picked.  So the Jig is doing a point selection with an object selection cursor to feed a point to a GetNestedEnitiy call with the NonInteractivePickPoint property.  This causes the thing the honor Osnaps and show the coordinates at the cursor with Dynamic Input on.

Second, I would really like to get rid of the GetNestedEntity calls entirely and use some sort of scanline to cast a ray through modelspace in the view direction and return the first object it hits.  That way, I would not have to call Editor.SwitchToModelSpace() and Editor.SwitchToPaperSpace() to jump in and out of the viewports to make the selection locally.  By the way, the reason I'm using GetNestedEntity instead of GetEntity is that GetEntity does not have a NonInteractivePickPoint property, for some unknown reason.  I was planing on creating a GetEntityThroughViewport extension method also that uses the GetNestedEntity calls internally and traverses up the blocks returned by GetContainers() to return the top level object.

Lastly, it does not take into consideration a viewport's clipping boundary.  I still need to find some point containment test function to test the viewports.  I think Luis wrote one of those a while back.  I did one in Lisp many years ago.

Ok, let's see if the code fits:

This is the main class with a few extension methods.
Code: [Select]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

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

namespace Zoltan
{
    internal static class SelectThroughViewport
    {
        public static PromptNestedEntityThroughViewportResult GetNestedEntityThroughViewport(this Editor acadEditor, string prompt)
        {
            return acadEditor.GetNestedEntityThroughViewport(new PromptNestedEntityThroughViewportOptions(prompt));
        }

        public static PromptNestedEntityThroughViewportResult GetNestedEntityThroughViewport(this Editor acadEditor, PromptNestedEntityThroughViewportOptions options)
        {
            Document acadDocument = acadEditor.Document;
            Database acadDatabase = acadDocument.Database;
            LayoutManager layoutManager = LayoutManager.Current;

            SelectThroughViewportJig stvpJig = new SelectThroughViewportJig(options);

            PromptResult pointResult = acadEditor.Drag(stvpJig);

            if (pointResult.Status == PromptStatus.OK)
            {
                Point3d pickedPoint = stvpJig.Result.Value;

                PromptNestedEntityOptions pneOpions = options.Options;
                pneOpions.NonInteractivePickPoint = pickedPoint;
                pneOpions.UseNonInteractivePickPoint = true;

                PromptNestedEntityResult pickResult = acadEditor.GetNestedEntity(pneOpions);

                if ((pickResult.Status == PromptStatus.OK) ||
                    (acadDatabase.TileMode) ||
                    (acadDatabase.PaperSpaceVportId != acadEditor.CurrentViewportObjectId))
                {
                    return new PromptNestedEntityThroughViewportResult(pickResult);
                }
                else
                {
                    SelectionFilter vportFilter = new SelectionFilter(new TypedValue[] { new TypedValue(0, "VIEWPORT"),
                                                                                                 new TypedValue(-4, "!="),
                                                                                                 new TypedValue(69, 1),
                                                                                                 new TypedValue(410, layoutManager.CurrentLayout) });

                    PromptSelectionResult vportResult = acadEditor.SelectAll(vportFilter);

                    if (vportResult.Status == PromptStatus.OK)
                    {
                        using (Transaction trans = acadDocument.TransactionManager.StartTransaction())
                        {
                            foreach (ObjectId objectId in vportResult.Value.GetObjectIds())
                            {
                                Viewport viewport = (Viewport)trans.GetObject(objectId, OpenMode.ForRead, false, false);

                                if (viewport.ContainsPoint(pickedPoint))
                                {
                                    pneOpions.NonInteractivePickPoint = TranslatePointPsToMs(viewport, pickedPoint);
                                    pneOpions.UseNonInteractivePickPoint = true;

                                    acadEditor.SwitchToModelSpace();

                                    Application.SetSystemVariable("CVPORT", viewport.Number);

                                    PromptNestedEntityResult pneResult = acadEditor.GetNestedEntity(pneOpions);

                                    acadEditor.SwitchToPaperSpace();

                                    if (pneResult.Status == PromptStatus.OK)
                                    {
                                        return new PromptNestedEntityThroughViewportResult(pneResult, objectId);
                                    }
                                }
                            }
                        }
                    }

                    return new PromptNestedEntityThroughViewportResult(pickResult);
                }
            }
            else
            {
                return new PromptNestedEntityThroughViewportResult(pointResult);
            }
        }

        private static Point3d TranslatePointPsToMs(Viewport viewport, Point3d psPoint)
        {
            Point3d msPoint = psPoint.TransformBy(Matrix3d.Displacement(new Vector3d(viewport.CenterPoint.X, viewport.CenterPoint.Y, viewport.CenterPoint.Z).Negate()))
                                     .TransformBy(Matrix3d.Scaling(1.0 / viewport.CustomScale, Point3d.Origin))
                                     .TransformBy(Matrix3d.Rotation(-viewport.TwistAngle, Vector3d.ZAxis, Point3d.Origin))
                                     .TransformBy(Matrix3d.Displacement(new Vector3d(viewport.ViewCenter.X, viewport.ViewCenter.Y, 0.0)))
                                     .TransformBy(Matrix3d.PlaneToWorld(new Plane(viewport.ViewTarget, viewport.ViewDirection)));



            return msPoint;
        }

        private static bool ContainsPoint(this Viewport viewport, Point3d point)
        {
            // TODO: Need to consider viewport clipping boundary

            return (((viewport.CenterPoint.X - (viewport.Width / 2.0)) <= point.X) &&
                    ((viewport.CenterPoint.X + (viewport.Width / 2.0)) >= point.X) &&
                    ((viewport.CenterPoint.Y - (viewport.Height / 2.0)) <= point.Y) &&
                    ((viewport.CenterPoint.Y + (viewport.Height / 2.0)) >= point.Y) &&
                    (viewport.CenterPoint.Z == point.Z));
        }

        private class SelectThroughViewportJig : DrawJig
        {
            private PromptNestedEntityThroughViewportOptions options;

            public SelectThroughViewportJig(PromptNestedEntityThroughViewportOptions options)
            {
                this.options = options;
            }

            public PromptPointResult Result { get; private set; }

            protected override SamplerStatus Sampler(JigPrompts prompts)
            {
                JigPromptPointOptions jigOptions = new JigPromptPointOptions(options.Message);
                jigOptions.Cursor = CursorType.EntitySelect;

                Result = prompts.AcquirePoint(jigOptions);

                if (Result.Status == PromptStatus.OK)
                {
                    return SamplerStatus.OK;
                }
                else if (Result.Status == PromptStatus.Cancel)
                {
                    return SamplerStatus.OK;
                }

                return SamplerStatus.NoChange;
            }

            protected override bool WorldDraw(Autodesk.AutoCAD.GraphicsInterface.WorldDraw draw)
            {
                return true;
            }
        }
    }
}

Here is the PromptNestedEntityThroughViewportOptions class that could be used to set up additional options in the future.  I ended up descending it from PromptOptions even though it is just a wrapper class for a PromptNestedEntityOptions field, because I wanted it to have commonality with the other PromptOptions classes in case you wanted to put it into a collection.  I really wished I could have inherited from the PromptNestedEntityOptions clas; would have made life easier.
Code: [Select]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Autodesk.AutoCAD.EditorInput;
using System.Collections;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Geometry;

namespace Zoltan
{
    internal sealed class PromptNestedEntityThroughViewportOptions : PromptOptions
    {
        private PromptNestedEntityOptions m_options;
       
        public PromptNestedEntityThroughViewportOptions(string message) : base(message)
        {
            this.m_options = new PromptNestedEntityOptions(message);
        }

        public PromptNestedEntityThroughViewportOptions(string messageAndKeywords, string globalKeywords)
            : base(messageAndKeywords, globalKeywords)
        {
            this.m_options = new PromptNestedEntityOptions(messageAndKeywords, globalKeywords);
        }

        public bool AllowNone
        {
            get
            {
                return this.m_options.AllowNone;
            }
            set
            {
                this.m_options.AllowNone = value;
            }
        }

        public new bool AppendKeywordsToMessage
        {
            get
            {
                return this.m_options.AppendKeywordsToMessage;
            }
            set
            {
                this.m_options.AppendKeywordsToMessage = value;
            }
        }

        public new bool IsReadOnly
        {
            get
            {
                return this.m_options.IsReadOnly;
            }
        }

        public new KeywordCollection Keywords
        {
            get
            {
                return this.m_options.Keywords;
            }
        }

        public new string Message
        {
            get
            {
                return this.m_options.Message;
            }
            set
            {
                this.m_options.Message = value;
            }
        }

        public Point3d NonInteractivePickPoint
        {
            get
            {
                return this.m_options.NonInteractivePickPoint;
            }
            set
            {
                this.m_options.NonInteractivePickPoint = value;
            }
        }

        public new void SetMessageAndKeywords(string messageAndKeywords, string globalKeywords)
        {
            this.m_options.SetMessageAndKeywords(messageAndKeywords, globalKeywords);
        }

        public bool UseNonInteractivePickPoint
        {
            get
            {
                return this.m_options.UseNonInteractivePickPoint;
            }
            set
            {
                this.m_options.UseNonInteractivePickPoint = value;
            }
        }

        public PromptNestedEntityOptions Options
        {
            get
            {
                return this.m_options;
            }
        }
    }
}

This is the PromptNestedEntityThroughViewportResult that is really just a copy of the PromptNestedEntityResult class with an additional property for the ObjectId of the Viewport where the pick was made.  Here again I wised I could have descended from any of the PromptResult classes, but none of them have public constructors.  So keeping with the theme, I made it have internal constructors aswell.
Code: [Select]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;

namespace Zoltan
{
    internal sealed class PromptNestedEntityThroughViewportResult
    {
        private PromptStatus m_status;
        private string m_stringResult;
        private Point3d m_pickPoint;
        private ObjectId m_value;
        private ObjectId[] m_containers;
        private Matrix3d m_mat;
        private ObjectId m_viewport;

        internal PromptNestedEntityThroughViewportResult(PromptNestedEntityResult pneResult)
        {
            m_status = pneResult.Status;
            m_stringResult = pneResult.StringResult;
            m_pickPoint = pneResult.PickedPoint;
            m_value = pneResult.ObjectId;
            m_containers = pneResult.GetContainers();
            m_mat = pneResult.Transform;
            m_viewport = Autodesk.AutoCAD.DatabaseServices.ObjectId.Null;
        }

        internal PromptNestedEntityThroughViewportResult(PromptResult pneResult)
        {
            m_status = pneResult.Status;
            m_stringResult = pneResult.StringResult;
            m_pickPoint = Point3d.Origin;
            m_value = Autodesk.AutoCAD.DatabaseServices.ObjectId.Null;
            m_containers = null;
            m_mat = Matrix3d.Identity;
            m_viewport = Autodesk.AutoCAD.DatabaseServices.ObjectId.Null;
        }

        internal PromptNestedEntityThroughViewportResult(PromptNestedEntityResult pneResult, ObjectId viewport)
        {
            m_status = pneResult.Status;
            m_stringResult = pneResult.StringResult;
            m_pickPoint = pneResult.PickedPoint;
            m_value = pneResult.ObjectId;
            m_containers = pneResult.GetContainers();
            m_mat = pneResult.Transform;
            m_viewport = viewport;
        }

        public override string ToString()
        {
            return this.ToString(null);
        }

        public string ToString(IFormatProvider provider)
        {
            object[] args = new object[6];

            args[0] = m_status;
            args[1] = m_stringResult;
            args[2] = m_value;
            args[3] = m_pickPoint;
            args[4] = m_mat;
            args[5] = m_containers;

            return string.Format(provider, "({0},{1},{2},{3},{4},{5})", args);
        }

        public PromptStatus Status
        {
            get
            {
                return m_status;
            }
        }

        public string StringResult
        {
            get
            {
                return m_stringResult;
            }
        }

        public ObjectId ObjectId
        {
            get
            {
                return m_value;
            }
        }

        public Point3d PickedPoint
        {
            get
            {
                return m_pickPoint;
            }
        }

        public ObjectId[] GetContainers()
        {
            return m_containers;
        }

        public Matrix3d Transform
        {
            get
            {
                return m_mat;
            }
        }

        public ObjectId ViewportId
        {
            get
            {
                return m_viewport;
            }
        }
    }
}

Hope you enjoy it.