Monday, June 30, 2014

Open A Viewport From ModelSpace

I posted an article on selecting entities in ModelSpace that are visible from a PaperSpace Viewport a while ago. The key point of that article is to identify an area in ModelSpace that is visible in a "Display-Locked" Viewport in PaperSpace.

While writing that article, I was thinking it would be another topic that I would write on: opening a Viewport based on selected area in ModelSpace.

When presenting content in ModelSpace on a layout, user usually:
  • Set a selected layout current;
  • Open a Viewport;
  • Activate the Viewport into with "MSpace";
  • Twist the view angle if necessary;
  • Scale the view in the Viewport;
  • Pan the view in the Viewport to fit the desired drawing content to be shown in the Viewport;
  • Finally, lock the Viewport's display.
I was thinking that the process can be simplified like this:
  • In ModelSpace, user select an area with a polygon that is to be shown in a PaperSpace Viewport. It would be similar to AutoCAD's polygon selection;
  • User select an angle that the view would be twisted in Viewport;
  • User specify the view scale in Viewport;
  • A Viewport will be opened on given layout that scaled and twisted properly to fit the selected area/polygon, which encloses the drawing content to be shown in the opened Viewport.
As you can see, since the user usually already knows the view scale and the view twist angle when he/she is ready to present drawing content in Viewports on layout, the proposed process means that user just need to specify what area to be shown, a Viewport would be opened with proper size, scale and twist angle.

With this topic hanging in my mind for a few months, I finally found a bit time to actually write some code to see it in action.

First, I defined a class ViewportBoundaryInfo that represents a rectangle Viewport being projected into ModelSpace:

using Autodesk.AutoCAD.Geometry;
 
namespace OpenViewportFromModelSpace
{
    public class ViewportBoundaryInfo
    {
        public Point3d Centre
        {
            get
            {
                double x =
                    (PointLL.X + PointLU.X + PointRL.X + PointRU.X) / 4.0;
                double y =
                    (PointLL.Y + PointLU.Y + PointRL.Y + PointRU.Y) / 4.0;
                double z =
                    (PointLL.Z + PointLU.Z + PointRL.Z + PointRU.Z) / 4.0;
 
                return new Point3d(x, y, z);
            }
        }
        public Point3d PointLL { setget; }
 
        public Point3d PointLU { setget; }
 
        public Point3d PointRU { setget; }
 
        public Point3d PointRL { setget; }
 
        public double Rotation { setget; }
 
        public double Width
        {
            get { return PointLL.DistanceTo(PointRL); }
        }
 
        public double Height
        {
            get { return PointLL.DistanceTo(PointLU); }
        }
 
        public Point3dCollection RectanglePointsInModel
        {
            get
            {
                Point3dCollection points = new Point3dCollection();
                points.Add(PointLL);
                points.Add(PointRL);
                points.Add(PointRU);
                points.Add(PointLU);
                return points;
            }
        }
 
        public double TwistAngle { setget; }
 
        public ViewportBoundaryInfo()
        {
            PointLL = new Point3d();
            PointLU = new Point3d();
            PointRL = new Point3d();
            PointRU = new Point3d();
            TwistAngle = 0.0;
        }
    }
 
}

Then I decided to design a class that collect user input on which drawing area in ModelSpace to be shown in a Viewport. As the result of user input, the boundary of Viewport to be created would be the output. User also get chance to see the boundary rectangle before proceeding to actually create the Viewport. Here is the class OpenViewportVisualHelper:

using System;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;
using CadDb = Autodesk.AutoCAD.DatabaseServices;
 
namespace OpenViewportFromModelSpace
{
    public class OpenViewportVisualHelper : IDisposable 
    {
        private Document _dwg;
        private Editor _ed;
        private Database _db;
        private TransientManager _tsManager;
 
        private ViewportBoundaryInfo _vpBoundaryInfo = null;
 
        private Point3dCollection _polygonPoints = null;
        private CadDb.Polyline _polygon = null;
        private CadDb.Polyline _rectangle = null;
        public OpenViewportVisualHelper(Document dwg)
        {
            _dwg=dwg;
            _ed=dwg.Editor;
            _db=dwg.Database;
            _tsManager = TransientManager.CurrentTransientManager;
        }
 
        public ViewportBoundaryInfo ViewportBoundaryInfo
        {
            get { return _vpBoundaryInfo; }
        }
 
        public bool PickEnclosingPolygon()
        {
            ViewportBoundaryInfo vportInfo = new ViewportBoundaryInfo();
 
            if (!PickEnclosingPoints()) return false;
 
            if (_polygon != null)
            {
                double twistAngle;
                if (PickTwistAngle(_ed, out twistAngle))
                {
                    Point3d pLL, pRL, pRU, pLU;
                    GetTwistedBoundingBox(_polygon, twistAngle,
                        out pLL, out pRL, out pRU, out pLU);
 
                    vportInfo.PointLL = pLL;
                    vportInfo.PointRL = pRL;
                    vportInfo.PointRU = pRU;
                    vportInfo.PointLU = pLU;
 
                    vportInfo.TwistAngle = twistAngle;
                }
 
                _vpBoundaryInfo = vportInfo;
 
                DrawEnclosingPolygon(_polygonPoints);
                DrawViewportRectangle(vportInfo.RectanglePointsInModel);
                _ed.UpdateScreen();
 
                PromptKeywordOptions opt = new PromptKeywordOptions(
                    "\nDraw viewport boundary in ModelSpace permanently?");
                opt.AppendKeywordsToMessage = true;
                opt.Keywords.Add("Yes");
                opt.Keywords.Add("No");
                opt.Keywords.Default = "Yes";
                PromptResult res = _ed.GetKeywords(opt);
                bool ok = true;
                if (res.Status == PromptStatus.OK)
                {
                    if (res.StringResult == "Yes")
                    {
                        AddRectangleToModelSpace(_rectangle);
                    }
                }
                else
                {
                    ok = false;
                }
 
                _tsManager.EraseTransient(
                    _polygon, new IntegerCollection());
                _tsManager.EraseTransient(
                    _rectangle, new IntegerCollection());
 
                _polygon.Dispose();
                _rectangle.Dispose();
 
                return ok;
            }
            else
            {
                return false;
            }
        }
 
        public void Dispose()
        {
            if (_polygon!=null)
            {
                _tsManager.EraseTransient(_polygon, new IntegerCollection());
                _polygon.Dispose();
            }
 
            if (_rectangle!=null)
            {
                _tsManager.EraseTransient(_rectangle, new IntegerCollection());
                _rectangle.Dispose();
            }
        }
 
        #region private methods
 
        private bool PickEnclosingPoints()
        {
            bool ok = true;
            _polygonPoints = new Point3dCollection();
 
            _ed.PointMonitor += MyPointMonitor;
 
            while(true)
            {
                string msg;
                PromptPointOptions opt;
 
                if (_polygonPoints.Count == 0)
                {
                    msg = "Select first point of enclosing polyline:";
                    opt = new PromptPointOptions("\n" + msg);
                }
                else if (_polygonPoints.Count == 1)
                {
                    msg = "Select second point of enclosing polyline:";
                    opt = new PromptPointOptions("\n" + msg);
                    opt.UseBasePoint = true;
                    opt.BasePoint = _polygonPoints[0];
                    opt.UseDashedLine = true;
                }
                else
                {
                    msg = "select next point of enclosing polyline:";
                    opt = new PromptPointOptions("\n" + msg);
                }
 
                if (_polygonPoints.Count > 2)
                {
                    opt.Keywords.Add("Done");
                    opt.Keywords.Default = "Done";
                    opt.AllowNone = true;
                }
 
                PromptPointResult res = _ed.GetPoint(opt);
                if (res.Status==PromptStatus.OK || res.Status==PromptStatus.Keyword)
                {
                    if (res.Status == PromptStatus.OK)
                    {
                        _polygonPoints.Add(res.Value);
                    }
                    else
                    {
                        break;
                    }
                }
                else
                {
                    ok = false;
                    break;
                }
            }
 
            _ed.PointMonitor -= MyPointMonitor;
 
            DrawEnclosingPolygon(_polygonPoints);
 
            return ok;
        }
 
        private void MyPointMonitor(object sender, PointMonitorEventArgs e)
        {
            Point3dCollection points = new Point3dCollection();
            foreach (Point3d p in _polygonPoints)
            {
                points.Add(p);
            }
            points.Add(e.Context.RawPoint);
            DrawEnclosingPolygon(points);
        }
 
        private void DrawEnclosingPolygon(Point3dCollection points)
        {
            if (_polygon != null)
            {
                _tsManager.EraseTransient(_polygon, new IntegerCollection());
                _polygon.Dispose();
                _polygon = null;
            }
 
            _polygon = CreatePolygonFromPoints(points);
            _tsManager.AddTransient(
                _polygon, 
                TransientDrawingMode.DirectTopmost, 
                128, 
                new IntegerCollection());
        }
 
        private void DrawViewportRectangle(Point3dCollection points)
        {
            if (_rectangle!=null)
            {
                _tsManager.EraseTransient(_rectangle, new IntegerCollection());
                _rectangle.Dispose();
                _rectangle = null;
            }
 
            _rectangle = CreatePolygonFromPoints(points);
            _tsManager.AddTransient(
                _rectangle, 
                TransientDrawingMode.DirectTopmost, 
                128, 
                new IntegerCollection());
        }
 
        private CadDb.Polyline CreatePolygonFromPoints(Point3dCollection points)
        {
            CadDb.Polyline pl = new CadDb.Polyline(points.Count);
 
            for (int i = 0; i < points.Count; i++ )
            {
                pl.AddVertexAt(
                    i, new Point2d(points[i].X, points[i].Y), 0.0, 0.0, 0.0);
            }
 
            pl.ColorIndex = 2;
            if (pl.NumberOfVertices > 2) pl.Closed = true;
             
            return pl;
        }
 
        private bool PickTwistAngle(Editor ed, out double twistAngle)
        {
            twistAngle = 0.0;
 
            Point3d pt1, pt2;
 
            ed.WriteMessage(
                "\nDetermine viewport twist angle by " +
                "selecting 2 points as a horizontal line in viewport.");
            PromptPointOptions opt = new PromptPointOptions(
                "\nSelect first point:");
 
            PromptPointResult res = ed.GetPoint(opt);
            if (res.Status == PromptStatus.OK)
            {
                pt1 = res.Value;
 
                opt = new PromptPointOptions("\nSelect second point:");
                opt.UseBasePoint = true;
                opt.UseDashedLine = true;
                opt.BasePoint = pt1;
 
                res = ed.GetPoint(opt);
                if (res.Status == PromptStatus.OK)
                {
                    pt2 = res.Value;
 
                    //We can calculate the angle based on the 2 points
                    //Being lazy, I use a line to get the angle
                    using (CadDb.Line line=new CadDb.Line(pt1,pt2))
                    {
                        twistAngle = line.Angle;
                    }
 
                    return true;
                }
            }
 
            return false;
        }
 
        private void GetTwistedBoundingBox(
            CadDb.Polyline pl, double twistAngle,
            out Point3d pointLL, out Point3d pointRL,
            out Point3d pointRU, out Point3d pointLU)
        {
 
            Point3d pt = pl.GetPoint3dAt(0);
            double ang = Math.PI * 2.0 - twistAngle;
            Matrix3d mt = Matrix3d.Rotation(ang, Vector3d.ZAxis, pt);
 
            //Clone the polyline
            CadDb.Polyline clone = pl.Clone() as CadDb.Polyline;
            using (clone)
            {
                clone.TransformBy(mt);
                Extents3d ext = clone.GeometricExtents;
                pointLL = (new Point3d(ext.MinPoint.X, ext.MinPoint.Y, 0.0))
                    .TransformBy(mt.Inverse());
                pointRL = (new Point3d(ext.MaxPoint.X, ext.MinPoint.Y, 0.0))
                    .TransformBy(mt.Inverse());
                pointRU = (new Point3d(ext.MaxPoint.X, ext.MaxPoint.Y, 0.0))
                    .TransformBy(mt.Inverse());
                pointLU = (new Point3d(ext.MinPoint.X, ext.MaxPoint.Y, 0.0))
                    .TransformBy(mt.Inverse());
            }
        }
 
        private void AddRectangleToModelSpace(CadDb.Polyline pl)
        {
            using (Transaction tran=
                _dwg.TransactionManager.StartTransaction())
            {
                BlockTableRecord model = (BlockTableRecord)tran.GetObject(
                    SymbolUtilityServices.GetBlockModelSpaceId(_db),
                    OpenMode.ForWrite);
                model.AppendEntity(pl);
                tran.AddNewlyCreatedDBObject(pl, true);
                tran.Commit();
            }
        }
 
        #endregion
    }
}

Since I used TransientGraphics to show the desired drawing content to be shown in the Viewport (the selecting polygon) and the Viewport boundary rectangle, thus temporary, non-database-residing entity (Polyline) is used. So, I implemented IDispose interface with this class. This way, I can use much simple "using {...}" block to make sure the temporary entity being disposed.

Now that I can correctly capture user input on what portion of drawing content in ModelSpace to be displayed in a Viewport, I can then write some code to open a Viewport on specific layout. The Viewport would show exactly the rectangle Viewport boundary the user wants. Here is the code in class CadHelper:

using System;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
namespace OpenViewportFromModelSpace
{ 
 public class CadHelper
 {
  public static string SelectLayout()
  {
   Document dwg = CadApp.DocumentManager.MdiActiveDocument;
   Editor ed = dwg.Editor;
 
   PromptStringOptions opt=new PromptStringOptions(
    "\nEnter the name of a layout, " +
    "where viewport will be created: ");
   opt.AllowSpaces = true;
 
   PromptResult res = ed.GetString(opt);
   if (res.Status == PromptStatus.OK)
   {
    return res.StringResult;
   }
   else
   {
    return null;
   }
  }
 
  public static double GetPaperToModelScale()
  {
   Document dwg = CadApp.DocumentManager.MdiActiveDocument;
   Editor ed = dwg.Editor;
 
   PromptDoubleOptions opt = new PromptDoubleOptions(
    "\nEnter PaperSpace scale for the viewport:");
   opt.AllowNegative = false;
   opt.AllowZero = false;
   opt.AllowNone = false;
   opt.AllowArbitraryInput = false;
   opt.DefaultValue = 1.0;
   opt.UseDefaultValue = true;
 
   PromptDoubleResult res = ed.GetDouble(opt);
   if (res.Status==PromptStatus.OK)
   {
    return res.Value;
   }
   else
   {
    return double.MinValue;
   }
  }
 
  public static ObjectId CreateRectangularViewport(
   string layout, string layer, 
   double paperToModelScale,
   ViewportBoundaryInfo vportInfo)
  {
   //Create a vewport in the center of the layout
   Document dwg=CadApp.DocumentManager.MdiActiveDocument;
   ObjectId vpId = OpenViewport(
    dwg, 
    layout, 
    layer, 
    vportInfo.TwistAngle, 
    paperToModelScale, 
    vportInfo.Centre);
 
   //Resize the viewport
   ResizeViewport(dwg, vpId, vportInfo, paperToModelScale);
 
   return ObjectId.Null;
  }
 
  #region code borrowed from www.theswamp.org
  //**********************************************************************
  //Create coordinate transform matrix 
  //between modelspace and paperspace viewport
 
  //The code is borrowed from
  //http://www.theswamp.org/index.php?topic=34590.msg398539#msg398539
  //*********************************************************************
  public static Matrix3d PaperToModel(Viewport vp)
  {
   Matrix3d mx = ModelToPaper(vp);
   return mx.Inverse();
  }
 
  public static Matrix3d ModelToPaper(Viewport vp)
  {
   Vector3d vd = vp.ViewDirection;
   Point3d vc = new Point3d(vp.ViewCenter.X, vp.ViewCenter.Y, 0);
   Point3d vt = vp.ViewTarget;
   Point3d cp = vp.CenterPoint;
   double ta = -vp.TwistAngle;
   double vh = vp.ViewHeight;
   double height = vp.Height;
   double width = vp.Width;
   double scale = vh / height;
   double lensLength = vp.LensLength;
   Vector3d zaxis = vd.GetNormal();
   Vector3d xaxis = Vector3d.ZAxis.CrossProduct(vd);
   Vector3d yaxis;
 
   if (!xaxis.IsZeroLength())
   {
    xaxis = xaxis.GetNormal();
    yaxis = zaxis.CrossProduct(xaxis);
   }
   else if (zaxis.Z < 0)
   {
    xaxis = Vector3d.XAxis * -1;
    yaxis = Vector3d.YAxis;
    zaxis = Vector3d.ZAxis * -1;
   }
   else
   {
    xaxis = Vector3d.XAxis;
    yaxis = Vector3d.YAxis;
    zaxis = Vector3d.ZAxis;
   }
   Matrix3d pcsToDCS = Matrix3d.Displacement(Point3d.Origin - cp);
   pcsToDCS = pcsToDCS * Matrix3d.Scaling(scale, cp);
   Matrix3d dcsToWcs = Matrix3d.Displacement(vc - Point3d.Origin);
   Matrix3d mxCoords = Matrix3d.AlignCoordinateSystem(
    Point3d.Origin, Vector3d.XAxis, Vector3d.YAxis, 
                Vector3d.ZAxis, Point3d.Origin,
    xaxis, yaxis, zaxis);
   dcsToWcs = mxCoords * dcsToWcs;
   dcsToWcs = Matrix3d.Displacement(vt - Point3d.Origin) * dcsToWcs;
   dcsToWcs = Matrix3d.Rotation(ta, zaxis, vt) * dcsToWcs;
 
   Matrix3d perspectiveMx = Matrix3d.Identity;
   if (vp.PerspectiveOn)
   {
    double vSize = vh;
    double aspectRatio = width / height;
    double adjustFactor = 1.0 / 42.0;
    double adjstLenLgth = vSize * lensLength *
     Math.Sqrt(1.0 + aspectRatio * aspectRatio) * adjustFactor;
    double iDist = vd.Length;
    double lensDist = iDist - adjstLenLgth;
                                double[] dataAry = new double[] 
                                        {   
                                            1,0,0,0,0,1,0,0,0,0,
         (adjstLenLgth-lensDist)/adjstLenLgth,
                                            lensDist*(iDist-adjstLenLgth)/adjstLenLgth,
         0,0,-1.0/adjstLenLgth,iDist/adjstLenLgth
                                        };
 
    perspectiveMx = new Matrix3d(dataAry);
   }
 
   Matrix3d finalMx = 
                pcsToDCS.Inverse() * perspectiveMx * dcsToWcs.Inverse();
 
   return finalMx;
  }
 
  #endregion
 
  #region private methods
 
  private static void GetTwistedBoundingBox(
   Polyline pl, double twistAngle,
   out Point3d pointLL, out Point3d pointRL, 
   out Point3d pointRU, out Point3d pointLU)
  {
   
   Point3d pt = pl.GetPoint3dAt(0);
   double ang = Math.PI * 2.0 - twistAngle;
   Matrix3d mt = Matrix3d.Rotation(ang, Vector3d.ZAxis, pt);
 
   //Clone the polyline
   Polyline clone = pl.Clone() as Polyline;
   using (clone)
   {
    clone.TransformBy(mt);
    Extents3d ext = clone.GeometricExtents;
    pointLL = (new Point3d(ext.MinPoint.X, ext.MinPoint.Y, 0.0))
     .TransformBy(mt.Inverse());
    pointRL = (new Point3d(ext.MaxPoint.X, ext.MinPoint.Y, 0.0))
     .TransformBy(mt.Inverse());
    pointRU = (new Point3d(ext.MaxPoint.X, ext.MaxPoint.Y, 0.0))
     .TransformBy(mt.Inverse());
    pointLU = (new Point3d(ext.MinPoint.X, ext.MaxPoint.Y, 0.0))
     .TransformBy(mt.Inverse());
   }
  }
 
  private static ObjectId OpenViewport(
   Document dwg, string layoutName, string layerName, 
   double twistAngle, double pScale, Point3d targetCenter)
  {
   Viewport vport = null;
 
   using (Transaction tran = 
    dwg.Database.TransactionManager.StartTransaction())
   {
    BlockTableRecord layoutBlock = 
     GetLayoutBlock(tran, dwg.Database, layoutName);
 
    double width;
    double height;
    Point3d centre;
    GetLayoutSize(
     dwg, layoutName, out centre, out width, out height);
 
    vport = new Viewport();
 
    vport.CenterPoint = new Point3d(centre.X, centre.Y, 0.0);
    vport.Width = width;
    vport.Height = height;
    vport.Layer = layerName;
 
    layoutBlock.UpgradeOpen();
    layoutBlock.AppendEntity(vport);
    tran.AddNewlyCreatedDBObject(vport, true);
 
    vport.On = true; ;
    vport.ViewDirection = Vector3d.ZAxis;
    vport.ViewTarget = targetCenter;
    vport.ViewCenter = Point2d.Origin;
    vport.TwistAngle = Math.PI * 2 - twistAngle;
    vport.CustomScale = pScale;
    vport.Locked = true;
 
    vport.UpdateDisplay();
 
    tran.Commit();
   }
 
   return vport.ObjectId;
  }
 
  private static void ResizeViewport(
   Document dwg, ObjectId vpId,
   ViewportBoundaryInfo vportInfo,
   double customScale)
  {
   Matrix3d mt = GetModelToPaperTransformMatrix(vpId);
 
   Point3d pt1;
   Point3d pt2;
 
   //Get vport width
   pt1 = vportInfo.PointLL.TransformBy(mt);
   pt2 = vportInfo.PointRL.TransformBy(mt);
 
   double vportW = Math.Abs(pt2.X - pt1.X); ;
 
   //Get vport height
   pt1 = vportInfo.PointLL.TransformBy(mt);
   pt2 = vportInfo.PointLU.TransformBy(mt);
 
   double vportH = Math.Abs(pt2.Y - pt1.Y);
 
   using (Transaction tran = 
    dwg.Database.TransactionManager.StartTransaction())
   {
    Viewport vport = (Viewport)
     tran.GetObject(vpId, OpenMode.ForWrite);
 
    Point3d center = vport.CenterPoint;
 
    vport.Locked = false;
    vport.Width = vportW;
    vport.Height = vportH;
    vport.CenterPoint = center;
    vport.CustomScale = customScale;
    vport.Locked = true;
 
    vport.UpdateDisplay();
 
    tran.Commit();
   }
  }
 
  private static void GetLayoutSize(
   Document dwg, string layoutName, 
   out Point3d centre, out double width, out double height)
  {
   centre = new Point3d();
   width = 0.0;
   height = 0.0;
 
   ObjectId layoutId = LayoutManager.Current.GetLayoutId(layoutName);
 
   using (Transaction tran=dwg.TransactionManager.StartTransaction())
   {
    Layout layout = (Layout)
     tran.GetObject(layoutId, OpenMode.ForRead);
 
    double w = 
     layout.PlotPaperSize.X - 
     layout.PlotPaperMargins.MinPoint.X - 
     layout.PlotPaperMargins.MaxPoint.X;
    double h = layout.PlotPaperSize.Y - 
     layout.PlotPaperMargins.MinPoint.Y - 
     layout.PlotPaperMargins.MaxPoint.Y;
 
    double s = layout.StdScale;
    if (!layout.UseStandardScale)
    {
     s = layout.CustomPrintScale.Denominator / 
      layout.CustomPrintScale.Numerator;
    }
 
    if (layout.PlotRotation==PlotRotation.Degrees090 || 
     layout.PlotRotation==PlotRotation.Degrees270)
    {
     width = h;
     height = w;
    }
    else
    {
     width = w;
     height = h;
    }
 
    width = width * s;
    height = height * s;
 
    if (layout.PlotPaperUnits==PlotPaperUnit.Inches)
    {
     width = width / 25.4;
     height = height / 25.4;
    }
    else if (layout.PlotPaperUnits==PlotPaperUnit.Pixels)
    {
     width = width / 25.4 * 72.0;
     height = height / 25.4 * 72.0;
    }
 
    centre = new Point3d(width / 2.0, height / 2.0, 0.0);
 
    tran.Commit();
   }
  }
 
  private static BlockTableRecord GetLayoutBlock(
   Transaction tran, Database db, string layoutName)
  {
   LayoutManager lmanager = LayoutManager.Current;
   ObjectId layId = lmanager.GetLayoutId(layoutName);
 
   Layout layout = tran.GetObject(layId, OpenMode.ForRead) as Layout;
 
   return tran.GetObject(
    layout.BlockTableRecordId, OpenMode.ForRead) 
    as BlockTableRecord;
  }
 
  private static Matrix3d GetModelToPaperTransformMatrix(
   ObjectId vportId)
  {
   Matrix3d mt;
 
   using (Transaction tran = 
    vportId.Database.TransactionManager.StartTransaction())
   {
    Viewport vp = (Viewport)
     tran.GetObject(vportId, OpenMode.ForRead);
    mt = ModelToPaper(vp);
    tran.Commit();
   }
 
   return mt;
  }
 
  private static void TransformPointsFromModelToPaper(
   ObjectId vpId, 
   Point3dCollection modelPoints1, 
   Point3dCollection modelPoints2,
   out Point3dCollection paperPoints1, 
   out Point3dCollection paperPoints2)
  {
   paperPoints1 = new Point3dCollection();
   paperPoints2 = new Point3dCollection();
   Matrix3d mt;
   using (Transaction tran =
    vpId.Database.TransactionManager.StartTransaction())
   {
    Viewport vp=(Viewport)tran.GetObject(vpId,OpenMode.ForRead);
    mt = ModelToPaper(vp);
    tran.Commit();
   }
 
   foreach (Point3d p in modelPoints1)
   {
    paperPoints1.Add(p.TransformBy(mt));
   }
 
   foreach (Point3d p in modelPoints2)
   {
    paperPoints2.Add(p.TransformBy(mt));
   }
  }
 
  #endregion
 }
}

As the code shows, the Viewport will be opened at the centre of the given layout.

Finally with all the code ready to use, here is the CommandClass MyCadCommands:

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
[assemblyCommandClass(typeof(OpenViewportFromModelSpace.MyCadCommands))]
 
namespace OpenViewportFromModelSpace
{
    public class MyCadCommands
    {
        [CommandMethod("OpenVp")]
        public static void CreateRectangularViewport()
        {
            Document dwg = CadApp.DocumentManager.MdiActiveDocument;
            Editor ed = dwg.Editor;
 
            //Make sure start from ModelSpace
            LayoutManager.Current.CurrentLayout = "Model";
            
            //Pick model area to be shown in viewport
            ViewportBoundaryInfo vportInfo = GetUserInput(dwg);
           
            //Ask user for Layout name, exception test is omitted
            string layoutName = CadHelper.SelectLayout();
 
            //layer for the viewport, exception test is omitted
            string layerName = (string)
                Application.GetSystemVariable("CLAYER");
 
            //Viewport's custom scale, exception test is omitted
            double paperToModelScale = CadHelper.GetPaperToModelScale();
 
            if (!string.IsNullOrEmpty(layoutName) &&
                paperToModelScale != double.MinValue)
            {
                //Create viewport on specified layout
                try
                {
                    System.Windows.Forms.Cursor.Current =
                        System.Windows.Forms.Cursors.WaitCursor;
 
                    LayoutManager.Current.CurrentLayout = layoutName;
 
                    CadHelper.CreateRectangularViewport(
                        layoutName, layerName,
                        paperToModelScale, vportInfo);
                }
                catch (System.Exception ex)
                {
                    ed.WriteMessage(
                        "\nError: {0}.\n\n{1}", ex.Message, ex.StackTrace);
                }
                finally
                {
                    System.Windows.Forms.Cursor.Current =
                        System.Windows.Forms.Cursors.Default;
                }
            }
            else
            {
                ed.WriteMessage("\n*Cancel*");
            }
 
            Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt();
        }
 
        [CommandMethod("MyPolygon")]
        public static void DrawVisualPolygon()
        {
            Document dwg = CadApp.DocumentManager.MdiActiveDocument;
            Editor ed = dwg.Editor;
 
            GetUserInput(dwg);
        }
 
        private static ViewportBoundaryInfo GetUserInput(Document dwg)
        {
            ViewportBoundaryInfo vpInfo = null;
            using (OpenViewportVisualHelper helper =
                new OpenViewportVisualHelper(dwg))
            {
                if (helper.PickEnclosingPolygon())
                {
                    vpInfo = helper.ViewportBoundaryInfo;
                }
            }
 
            return vpInfo;
        }
    }
}

Watch this video clip to see how the code works.

In order to make the code shown here more user friendly in real AutoCAD operation, I could give user more options, such as after showing the Viewport boundary rectangle, I could let user drag/rotate the rectangle. so that the Viewport to be created would better fit the drawing content to be shown.

Download source code here.

2 comments:

Olivier Eckmann said...

Hi Norman,

Nice code. I've a little question connected to this feature. I would like to know if it's possible to restore an exiting layerstate in the viewport created. Because I try to use LayerStateManager to restore but I can't apply a layerstate to a specified viewportId.

Thanks and great job again.

Olivier

Sorry for my english, not my native langage.

Norman Yuan said...

Hi Olivier,

I noticed your post for the question in Autodesk discussion forum. I never wrote code to save/restore layer states with specific viewport. Most of my topics of interest in inspired from a real need of production in my CAD related work.

Now that you asked, I may give it a try when I can spare some time. If anything useful, I'll post a reply to your post in Autodesk discussion forum.

Norman

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.