Saturday, March 15, 2014

Selecting Entities In ModelSpace Through Viewport

Very often, we need to select entities in ModelSpace, which are visible in a given Viewport on a layout. That is, we want to project the viewport's boundary (as a rectangle, or as a non-rectangle polygon) into ModelSpace and find all entities inside, fully or partially.

In other case, given a point, or an entity, in ModelSpace, we may want to determine which viewport or viewports on a layout the point/entity can be seen.

Note: there is a post in Autodesk's user forum on this topic.

I began with code that collects Viewport information on a given layout. The information is used to determine which entities in ModelSpace are visible through each Viewport, and is collected in one single transaction. Here is the code:

    1 using Autodesk.AutoCAD.ApplicationServices;
    2 using Autodesk.AutoCAD.DatabaseServices;
    3 using Autodesk.AutoCAD.EditorInput;
    4 using Autodesk.AutoCAD.Geometry;
    5 using System;
    6 using System.Collections.Generic;
    7 
    8 namespace EntitiesInsideViewport
    9 {
   10     //Class to hold Viewport information, obtained
   11     //in single Transaction
   12     public class ViewportInfo
   13     {
   14         public ObjectId ViewportId { set; get; }
   15         public ObjectId NonRectClipId { set; get; }
   16         public Point3dCollection BoundaryInPaperSpace { set; get; }
   17         public Point3dCollection BoundaryInModelSpace { set; get; }
   18     }
   19 
   20     public class CadHelper
   21     {
   22         //Get needed Viewport information
   23         public static ViewportInfo[] SelectLockedViewportInfoOnLayout(
   24             Document dwg, string layoutName)
   25         {
   26             List<ViewportInfo> lst = new List<ViewportInfo>();
   27             TypedValue[] vals = new TypedValue[]{
   28                 new TypedValue((int)DxfCode.Start, "VIEWPORT"),
   29                 new TypedValue((int)DxfCode.LayoutName,layoutName)
   30             };
   31 
   32             PromptSelectionResult res =
   33                 dwg.Editor.SelectAll(new SelectionFilter(vals));
   34             if (res.Status==PromptStatus.OK)
   35             {
   36                 using (Transaction tran=
   37                     dwg.TransactionManager.StartTransaction())
   38                 {
   39                     foreach (ObjectId id in res.Value.GetObjectIds())
   40                     {
   41                         Viewport vport = (Viewport)tran.GetObject(
   42                             id, OpenMode.ForRead);
   43                         if (vport.Number!=1 && vport.Locked)
   44                         {
   45                             ViewportInfo vpInfo = new ViewportInfo();
   46                             vpInfo.ViewportId = id;
   47                             vpInfo.NonRectClipId = vport.NonRectClipEntityId;
   48                             if (!vport.NonRectClipEntityId.IsNull &&
   49                                 vport.NonRectClipOn)
   50                             {
   //                                 //Polyline2d pl = (Polyline2d)tran.GetObject(
   //                                 //    vport.NonRectClipEntityId, OpenMode.ForRead);
   52                                  Polyline pl = (Polyline)tran.GetObject(
   52                                     vport.NonRectClipEntityId, OpenMode.ForRead);
   53                                 vpInfo.BoundaryInPaperSpace =
   54                                     GetNonRectClipBoundary(pl, tran);
   55                             }
   56                             else
   57                             {
   58                                 vpInfo.BoundaryInPaperSpace =
   59                                     GetViewportBoundary(vport);
   60                             }
   61 
   62                             Matrix3d mt = PaperToModel(vport);
   63                             vpInfo.BoundaryInModelSpace =
   64                                 TransformPaperSpacePointToModelSpace(
   65                                 vpInfo.BoundaryInPaperSpace, mt);
   66 
   67                             lst.Add(vpInfo);
   68                         }
   69                     }
   70 
   71                     tran.Commit();
   72                 }
   73             }
   74 
   75             return lst.ToArray();
   76         }
   77 
   78         private static Point3dCollection GetViewportBoundary(Viewport vport)
   79         {
   80             Point3dCollection points = new Point3dCollection();
   81 
   82             Extents3d ext = vport.GeometricExtents;
   83             points.Add(new Point3d(ext.MinPoint.X, ext.MinPoint.Y, 0.0));
   84             points.Add(new Point3d(ext.MinPoint.X, ext.MaxPoint.Y, 0.0));
   85             points.Add(new Point3d(ext.MaxPoint.X, ext.MaxPoint.Y, 0.0));
   86             points.Add(new Point3d(ext.MaxPoint.X, ext.MinPoint.Y, 0.0));
   87 
   88             return points;
   89         }
   90 
   91         private static Point3dCollection GetNonRectClipBoundary(
   92             Polyline polyline, Transaction tran)
   93         {
   94             Point3dCollection points = new Point3dCollection();
   95 
   //             foreach (ObjectId vxId in polyline)
   //             {
   //                 Vertex2d vx = (Vertex2d)tran.GetObject(vxId, OpenMode.ForRead);
   //                 points.Add(polyline.VertexPosition(vx));
   //             }
   96             for (int i = 0; i < polyline.NumberOfvertices; i++)
   97             {
   98                 
   99                 points.Add(polyline.GetPoint3dAt(i);
  100             }
  101 
  102             return points;
  103         }
  104 
  105         private static Point3dCollection TransformPaperSpacePointToModelSpace(
  106             Point3dCollection paperSpacePoints, Matrix3d mt)
  107         {
  108             Point3dCollection points = new Point3dCollection();
  109 
  110             foreach (Point3d p in paperSpacePoints)
  111             {
  112                 points.Add(p.TransformBy(mt));
  113             }
  114 
  115             return points;
  116         }
  117 
  118         #region
  119         //**********************************************************************
  120         //Create coordinate transform matrix
  121         //between modelspace and paperspace viewport
  122         //The code is borrowed from
  123         //http://www.theswamp.org/index.php?topic=34590.msg398539#msg398539
  124         //*********************************************************************
  125         public static Matrix3d PaperToModel(Viewport vp)
  126         {
  127             Matrix3d mx = ModelToPaper(vp);
  128             return mx.Inverse();
  129         }
  130 
  131         public static Matrix3d ModelToPaper(Viewport vp)
  132         {
  133             Vector3d vd = vp.ViewDirection;
  134             Point3d vc = new Point3d(vp.ViewCenter.X, vp.ViewCenter.Y, 0);
  135             Point3d vt = vp.ViewTarget;
  136             Point3d cp = vp.CenterPoint;
  137             double ta = -vp.TwistAngle;
  138             double vh = vp.ViewHeight;
  139             double height = vp.Height;
  140             double width = vp.Width;
  141             double scale = vh / height;
  142             double lensLength = vp.LensLength;
  143             Vector3d zaxis = vd.GetNormal();
  144             Vector3d xaxis = Vector3d.ZAxis.CrossProduct(vd);
  145             Vector3d yaxis;
  146 
  147             if (!xaxis.IsZeroLength())
  148             {
  149                 xaxis = xaxis.GetNormal();
  150                 yaxis = zaxis.CrossProduct(xaxis);
  151             }
  152             else if (zaxis.Z &lt; 0)
  153             {
  154                 xaxis = Vector3d.XAxis * -1;
  155                 yaxis = Vector3d.YAxis;
  156                 zaxis = Vector3d.ZAxis * -1;
  157             }
  158             else
  159             {
  160                 xaxis = Vector3d.XAxis;
  161                 yaxis = Vector3d.YAxis;
  162                 zaxis = Vector3d.ZAxis;
  163             }
  164             Matrix3d pcsToDCS = Matrix3d.Displacement(Point3d.Origin - cp);
  165             pcsToDCS = pcsToDCS * Matrix3d.Scaling(scale, cp);
  166             Matrix3d dcsToWcs = Matrix3d.Displacement(vc - Point3d.Origin);
  167             Matrix3d mxCoords = Matrix3d.AlignCoordinateSystem(
  168                 Point3d.Origin, Vector3d.XAxis, Vector3d.YAxis,
  169                 Vector3d.ZAxis, Point3d.Origin,
  170                 xaxis, yaxis, zaxis);
  171             dcsToWcs = mxCoords * dcsToWcs;
  172             dcsToWcs = Matrix3d.Displacement(vt - Point3d.Origin) * dcsToWcs;
  173             dcsToWcs = Matrix3d.Rotation(ta, zaxis, vt) * dcsToWcs;
  174 
  175             Matrix3d perspectiveMx = Matrix3d.Identity;
  176             if (vp.PerspectiveOn)
  177             {
  178                 double vSize = vh;
  179                 double aspectRatio = width / height;
  180                 double adjustFactor = 1.0 / 42.0;
  181                 double adjstLenLgth = vSize * lensLength *
  182                     Math.Sqrt(1.0 + aspectRatio * aspectRatio) * adjustFactor;
  183                 double iDist = vd.Length;
  184                 double lensDist = iDist - adjstLenLgth;
  185                 double[] dataAry = new double[]
  186                 {
  187                     1,0,0,0,0,1,0,0,0,0,
  188                     (adjstLenLgth-lensDist)/adjstLenLgth,
  189                     lensDist*(iDist-adjstLenLgth)/adjstLenLgth,
  190                     0,0,-1.0/adjstLenLgth,iDist/adjstLenLgth
  191                 };
  192 
  193                 perspectiveMx = new Matrix3d(dataAry);
  194             }
  195 
  196             Matrix3d finalMx =
  197                 pcsToDCS.Inverse() * perspectiveMx * dcsToWcs.Inverse();
  198 
  199             return finalMx;
  200         }
  201 
  202         #endregion
  203     }
  204 }

Now the following code does 2 things we want to do very often: finding out which entities in ModelSpace are visible in which Viewport; and determining a given entity in ModelSpace is visible in which Viewports:

    1 using System.Collections.Generic;
    2 using Autodesk.AutoCAD.ApplicationServices;
    3 using Autodesk.AutoCAD.DatabaseServices;
    4 using Autodesk.AutoCAD.EditorInput;
    5 using Autodesk.AutoCAD.Geometry;
    6 using Autodesk.AutoCAD.Runtime;
    7 
    8 [assembly: CommandClass(typeof(EntitiesInsideViewport.MyCommands))]
    9 
   10 namespace EntitiesInsideViewport
   11 {
   12     public class MyCommands
   13     {
   14         //Use viewport boundary as selecting window/polygon
   15         //to find entities in modelspace visible in each viewport
   16         [CommandMethod("VpSelect")]
   17         public static void SelectByViewport()
   18         {
   19             Document dwg = Application.DocumentManager.MdiActiveDocument;
   20             Editor ed = dwg.Editor;
   21 
   22             //Save current layout name
   23             string curLayout = LayoutManager.Current.CurrentLayout;
   24 
   25             try
   26             {
   27                 //Get viewport information on current layout
   28                 ViewportInfo[] vports = GetViewportInfoOnCurrentLayout();
   29                 if (vports == null) return;
   30 
   31                 //Switch to modelspace
   32                 LayoutManager.Current.CurrentLayout = "Model";
   33 
   34                 //Select entities in modelspace that are visible
   35                 foreach (ViewportInfo vInfo in vports)
   36                 {
   37                     ObjectId[] ents = SelectEntitisInModelSpaceByViewport(
   38                         dwg, vInfo.BoundaryInModelSpace);
   39                     ed.WriteMessage("\n{0} entit{1} found via Viewport \"{2}\"",
   40                         ents.Length,
   41                         ents.Length > 1 ? "ies" : "y",
   42                         vInfo.ViewportId.ToString());
   43                 }
   44 
   45                 Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt();
   46             }
   47             catch (System.Exception ex)
   48             {
   49                 ed.WriteMessage("\nCommand \"VpSelect\" failed:");
   50                 ed.WriteMessage("\n{0}\n{1}", ex.Message, ex.StackTrace);
   51             }
   52             finally
   53             {
   54                 //Restore back to original layout
   55                 if (LayoutManager.Current.CurrentLayout!=curLayout)
   56                 {
   57                     LayoutManager.Current.CurrentLayout = curLayout;
   58                 }
   59 
   60                 Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt();
   61             }
   62         }
   63 
   64         //Determine a given entity in modelspace is visible in
   65         //which viewports
   66         [CommandMethod("GetViewports")]
   67         public static void FindContainingViewport()
   68         {
   69             Document dwg = Application.DocumentManager.MdiActiveDocument;
   70             Editor ed = dwg.Editor;
   71 
   72             //Switch to modelspace
   73             string curLayout = LayoutManager.Current.CurrentLayout;
   74 
   75             try
   76             {
   77                 //Get viewport information on current layout
   78                 ViewportInfo[] vports = GetViewportInfoOnCurrentLayout();
   79                 if (vports == null) return;
   80 
   81                 //Pick an entity in modelspace
   82                 LayoutManager.Current.CurrentLayout = "Model";
   83                 ObjectId entId = PickEntity(ed);
   84                 if (entId.IsNull)
   85                 {
   86                     ed.WriteMessage("\n*Cancel*");
   87                 }
   88                 else
   89                 {
   90                     //Find viewport in which the selected entity is visible
   91                     List&lt;ObjectId> lst = new List&lt;ObjectId>();
   92                     foreach (ViewportInfo vpInfo in vports)
   93                     {
   94                         if (IsEntityInsideViewportBoundary(
   95                             dwg, entId, vpInfo.BoundaryInModelSpace))
   96                         {
   97                             lst.Add(vpInfo.ViewportId);
   98                             ed.WriteMessage(
   99                                 "\nSelected entity is visible in viewport \"{0}\"",
  100                                 vpInfo.ViewportId.ToString());
  101                         }
  102                     }
  103 
  104                     if (lst.Count == 0)
  105                         ed.WriteMessage(
  106                             "\nSelected entity is not visible in all viewports");
  107                     else
  108                         ed.WriteMessage(
  109                             "\nSelected entity is visible in {0} viewport{1}.",
  110                             lst.Count, lst.Count > 1 ? "s" : "");
  111                 }
  112 
  113                 Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt();
  114             }
  115             catch (System.Exception ex)
  116             {
  117                 ed.WriteMessage("\nCommand \"GetViewports\" failed:");
  118                 ed.WriteMessage("\n{0}\n{1}", ex.Message, ex.StackTrace);
  119             }
  120             finally
  121             {
  122                 //Restore back to original layout
  123                 if (LayoutManager.Current.CurrentLayout != curLayout)
  124                 {
  125                     LayoutManager.Current.CurrentLayout = curLayout;
  126                 }
  127 
  128                 Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt();
  129             }
  130         }
  131 
  132         private static ViewportInfo[] GetViewportInfoOnCurrentLayout()
  133         {
  134             string layoutName = LayoutManager.Current.CurrentLayout;
  135             if (layoutName.ToUpper() == "MODEL")
  136             {
  137                 Application.ShowAlertDialog("Please set a layout as active layout!");
  138                 return null;
  139             }
  140             else
  141             {
  142                 Document dwg = Application.DocumentManager.MdiActiveDocument;
  143                 ViewportInfo[] vports =
  144                     CadHelper.SelectLockedViewportInfoOnLayout(dwg, layoutName);
  145                 if (vports.Length == 0)
  146                 {
  147                     Application.ShowAlertDialog(
  148                         "No locked viewport found on layout \"" + layoutName + "\".");
  149                     return null;
  150                 }
  151                 else
  152                 {
  153                     return vports;
  154                 }
  155             }
  156         }
  157 
  158         private static ObjectId[] SelectEntitisInModelSpaceByViewport(
  159             Document dwg, Point3dCollection boundaryInModelSpace)
  160         {
  161             ObjectId[] ids = null;
  162 
  163             using (Transaction tran=dwg.TransactionManager.StartTransaction())
  164             {
  165                 //Zoom to the extents of the viewport boundary in modelspace
  166                 //before calling Editor.SelectXxxxx()
  167                 ZoomToWindow(boundaryInModelSpace);
  168 
  169                 PromptSelectionResult res =
  170                     dwg.Editor.SelectCrossingPolygon(boundaryInModelSpace);
  171                 if (res.Status==PromptStatus.OK)
  172                 {
  173                     ids = res.Value.GetObjectIds();
  174                 }
  175 
  176                 //Restored to previous view (view before zoomming)
  177                 tran.Abort();
  178             }
  179 
  180             return ids;
  181         }
  182 
  183         private static void ZoomToWindow(Point3dCollection boundaryInModelSpace)
  184         {
  185             Extents3d ext =
  186                     GetViewportBoundaryExtentsInModelSpace(boundaryInModelSpace);
  187 
  188             double[] p1 = new double[] { ext.MinPoint.X, ext.MinPoint.Y, 0.00 };
  189             double[] p2 = new double[] { ext.MaxPoint.X, ext.MaxPoint.Y, 0.00 };
  190 
  191             dynamic acadApp = Application.AcadApplication;
  192             acadApp.ZoomWindow(p1, p2);
  193         }
  194 
  195         private static Extents3d GetViewportBoundaryExtentsInModelSpace(
  196             Point3dCollection points)
  197         {
  198             Extents3d ext = new Extents3d();
  199             foreach (Point3d p in points)
  200             {
  201                 ext.AddPoint(p);
  202             }
  203 
  204             return ext;
  205         }
  206 
  207         private static ObjectId PickEntity(Editor ed)
  208         {
  209             PromptEntityOptions opt =
  210                 new PromptEntityOptions("\nSelect an entity:");
  211             PromptEntityResult res = ed.GetEntity(opt);
  212             if (res.Status==PromptStatus.OK)
  213             {
  214                 return res.ObjectId;
  215             }
  216             else
  217             {
  218                 return ObjectId.Null;
  219             }
  220         }
  221 
  222         private static bool IsEntityInsideViewportBoundary(
  223             Document dwg, ObjectId entId, Point3dCollection boundaryInModelSpace)
  224         {
  225             bool inside = false;
  226             using (Transaction tran = dwg.TransactionManager.StartTransaction())
  227             {
  228                 //Zoom to the extents of the viewport boundary in modelspace
  229                 //before calling Editor.SelectXxxxx()
  230                 ZoomToWindow(boundaryInModelSpace);
  231 
  232                 PromptSelectionResult res =
  233                     dwg.Editor.SelectCrossingPolygon(boundaryInModelSpace);
  234                 if (res.Status == PromptStatus.OK)
  235                 {
  236                     foreach (ObjectId id in res.Value.GetObjectIds())
  237                     {
  238                         if (id==entId)
  239                         {
  240                             inside = true;
  241                             break;
  242                         }
  243                     }
  244                 }
  245 
  246                 //Restored to previous view (before zoomming)
  247                 tran.Abort();
  248             }
  249 
  250             return inside;
  251         } 
  252     }
  253 }

Following picture shows the drawing I test the code against:



Update on 2019-12-27: 

Code has been updated, referring to latest comment below. Grayed lines were removed, yellowed lines were replacement.

8 comments:

  1. that is useful code. I had to do that to find what xref blocks were visible in vp's for a prog, so could not use the select function of editor. I used a function that checked if a point was inside a polyline shape (not an actual pline, the geometry of one. That would be slower to test on lines and arcs or text crossing, and have not needed that yet. Fast selection of entities without drawing open in the editor remains and issue...

    ReplyDelete
  2. This Is exactly what I needed - you made my day. Side note, line 152 from "Create coordinate transform matrix" should read:
    152 else if (zaxis.Z < 0)
    not
    152 else if (zaxis.Z < 0)
    Thanks again

    ReplyDelete
  3. Argh. I meant to point out that you don't have the smaller than symbol showing on line 152, but the html markup

    ReplyDelete
  4. Norman, I know this is an old post, but I am having a heap of trouble with polygonal viewports and in particular line 47 and the Polyline2d part of the viewport.

    I am intently curious about the reason for trying to create a Polyline 2d. Any attempt to convert from the polyline to a Polyline2d fails...

    ReplyDelete
  5. Sorry for the late respond: I was travelling in middle of November and somehow missed your comment.

    I was also puzzled when I looked at the code when prompted by your question: why did I use Polyline2d, instead of Polyline then? and clearly the code worked then. Maybe the clip polygon was Polyline2d in older Acad version? (at that time I probably was using Acad2012).

    I updated the code to get the clip polygon as Polyline.

    ReplyDelete

  6. Please, do you have an example, moving objects from the model space to a specific viewport keeping its scale and vice versa?

    ReplyDelete