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.