Creating an AutoCAD layout with custom plot and viewport settings using .NET

This request from Thomas Longnecker languished in my inbox for several weeks before I finally found the time to work on it.

It would be tremendously helpful to me if you could give an explanatory example of how to: Create a new layout, add a page-setup with plot-settings and then either delete the default viewport and create a new one or possible change the default viewport.

[…]

Within the newly created layouts I needed to set some of the general Plot setting, mainly Paper size and Plot style table.

Some of the main things for the viewport was to set the viewing area and plot scale and then lock it.

It's clearly an area that's of general interest – many people need to automate the creation and configuration of layout tabs – but it's not an area I've spend much time looking at, myself. In the end I managed to put something together that works well – once again with a fair amount of help from the online documentation – and is hopefully fairly easy to understand: I've done my best to implement extension methods that should be useful for people in other contexts, which has helped keep the command implementation nice and succinct.

Here's the C# code:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.Runtime;

using System;

 

namespace LayoutCreation

{

  public static class Extensions

  {

    /// <summary>

    /// Reverses the order of the X and Y properties of a Point2d.

    /// </summary>

    /// <param name="flip">Boolean indicating whether to reverse or not.</param>

    /// <returns>The original Point2d or the reversed version.</returns>

 

    public static Point2d Swap(this Point2d pt, bool flip = true)

    {

      return flip ? new Point2d(pt.Y, pt.X) : pt;

    }

 

    /// <summary>

    /// Pads a Point2d with a zero Z value, returning a Point3d.

    /// </summary>

    /// <param name="pt">The Point2d to pad.</param>

    /// <returns>The padded Point3d.</returns>

 

    public static Point3d Pad(this Point2d pt)

    {

      return new Point3d(pt.X, pt.Y, 0);

    }

 

    /// <summary>

    /// Strips a Point3d down to a Point2d by simply ignoring the Z ordinate.

    /// </summary>

    /// <param name="pt">The Point3d to strip.</param>

    /// <returns>The stripped Point2d.</returns>

 

    public static Point2d Strip(this Point3d pt)

    {

      return new Point2d(pt.X, pt.Y);

    }

 

    /// <summary>

    /// Creates a layout with the specified name and optionally makes it current.

    /// </summary>

    /// <param name="name">The name of the viewport.</param>

    /// <param name="select">Whether to select it.</param>

    /// <returns>The ObjectId of the newly created viewport.</returns>

 

    public static ObjectId CreateAndMakeLayoutCurrent(

      this LayoutManager lm, string name, bool select = true

    )

    {

      // First try to get the layout

 

      var id = lm.GetLayoutId(name);

 

      // If it doesn't exist, we create it

 

      if (!id.IsValid)

      {

        id = lm.CreateLayout(name);

      }

 

      // And finally we select it

 

      if (select)

      {

        lm.CurrentLayout = name;

      }

 

      return id;

    }

 

    /// <summary>

    /// Applies an action to the specified viewport from this layout.

    /// Creates a new viewport if none is found withthat number.

    /// </summary>

    /// <param name="tr">The transaction to use to open the viewports.</param>

    /// <param name="vpNum">The number of the target viewport.</param>

    /// <param name="f">The action to apply to each of the viewports.</param>

 

    public static void ApplyToViewport(

      this Layout lay, Transaction tr, int vpNum, Action<Viewport> f

    )

    {

      var vpIds = lay.GetViewports();

      Viewport vp = null;

 

      foreach (ObjectId vpId in vpIds)

      {

        var vp2 = tr.GetObject(vpId, OpenMode.ForWrite) as Viewport;

        if (vp2 != null && vp2.Number == vpNum)

        {

          // We have found our viewport, so call the action

 

          vp = vp2;

          break;

        }

      }

 

      if (vp == null)

      {

        // We have not found our viewport, so create one

 

        var btr =

          (BlockTableRecord)tr.GetObject(

            lay.BlockTableRecordId, OpenMode.ForWrite

          );

 

        vp = new Viewport();

 

        // Add it to the database

 

        btr.AppendEntity(vp);

        tr.AddNewlyCreatedDBObject(vp, true);

 

        // Turn it - and its grid - on

 

        vp.On = true;

        vp.GridOn = true;

      }

 

      // Finally we call our function on it

 

      f(vp);     

    }

 

    /// <summary>

    /// Apply plot settings to the provided layout.

    /// </summary>

    /// <param name="pageSize">The canonical media name for our page size.</param>

    /// <param name="styleSheet">The pen settings file (ctb or stb).</param>

    /// <param name="devices">The name of the output device.</param>

 

    public static void SetPlotSettings(

      this Layout lay, string pageSize, string styleSheet, string device

    )

    {

      using (var ps = new PlotSettings(lay.ModelType))

      {

        ps.CopyFrom(lay);

 

        var psv = PlotSettingsValidator.Current;

 

        // Set the device

 

        var devs = psv.GetPlotDeviceList();

        if (devs.Contains(device))

        {

          psv.SetPlotConfigurationName(ps, device, null);

          psv.RefreshLists(ps);

        }

 

        // Set the media name/size

 

        var mns = psv.GetCanonicalMediaNameList(ps);

        if (mns.Contains(pageSize))

        {

          psv.SetCanonicalMediaName(ps, pageSize);

        }

 

        // Set the pen settings

 

        var ssl = psv.GetPlotStyleSheetList();

        if (ssl.Contains(styleSheet))

        {

          psv.SetCurrentStyleSheet(ps, styleSheet);

        }

 

        // Copy the PlotSettings data back to the Layout

 

        var upgraded = false;

        if (!lay.IsWriteEnabled)

        {

          lay.UpgradeOpen();

          upgraded = true;

        }

 

        lay.CopyFrom(ps);

 

        if (upgraded)

        {

          lay.DowngradeOpen();

        }

      }

 
0;  }

 

    /// <summary>

    /// Determine the maximum possible size for this layout.

    /// </summary>

    /// <returns>The maximum extents of the viewport on this layout.</returns>

 

    public static Extents2d GetMaximumExtents(this Layout lay)

    {

      // If the drawing template is imperial, we need to divide by

      // 1" in mm (25.4)

 

      var div = lay.PlotPaperUnits == PlotPaperUnit.Inches ? 25.4 : 1.0;

 

      // We need to flip the axes if the plot is rotated by 90 or 270 deg

 

      var doIt =

        lay.PlotRotation == PlotRotation.Degrees090 ||

        lay.PlotRotation == PlotRotation.Degrees270;

 

      // Get the extents in the correct units and orientation

 

      var min = lay.PlotPaperMargins.MinPoint.Swap(doIt) / div;

      var max =

        (lay.PlotPaperSize.Swap(doIt) -

         lay.PlotPaperMargins.MaxPoint.Swap(doIt).GetAsVector()) / div;

 

      return new Extents2d(min, max);

    }

 

    /// <summary>

    /// Sets the size of the viewport according to the provided extents.

    /// </summary>

    /// <param name="ext">The extents of the viewport on the page.</param>

    /// <param name="fac">Optional factor to provide padding.</param>

 

    public static void ResizeViewport(

      this Viewport vp, Extents2d ext, double fac = 1.0

    )

    {

      vp.Width = (ext.MaxPoint.X - ext.MinPoint.X) * fac;

      vp.Height = (ext.MaxPoint.Y - ext.MinPoint.Y) * fac;

      vp.CenterPoint =

        (Point2d.Origin + (ext.MaxPoint - ext.MinPoint) * 0.5).Pad();

    }

 

    /// <summary>

    /// Sets the view in a viewport to contain the specified model extents.

    /// </summary>

    /// <param name="ext">The extents of the content to fit the viewport.</param>

    /// <param name="fac">Optional factor to provide padding.</param>

 

    public static void FitContentToViewport(

      this Viewport vp, Extents3d ext, double fac = 1.0

    )

    {

      // Let's zoom to just larger than the extents

 

      vp.ViewCenter =

        (ext.MinPoint + ((ext.MaxPoint - ext.MinPoint) * 0.5)).Strip();

 

      // Get the dimensions of our view from the database extents

 

      var hgt = ext.MaxPoint.Y - ext.MinPoint.Y
;

      var wid = ext.MaxPoint.X - ext.MinPoint.X;

 

      // We'll compare with the aspect ratio of the viewport itself

      // (which is derived from the page size)

 

      var aspect = vp.Width / vp.Height;

 

      // If our content is wider than the aspect ratio, make sure we

      // set the proposed height to be larger to accommodate the

      // content

 

      if (wid / hgt > aspect)

      {

        hgt = wid / aspect;

      }

 

      // Set the height so we're exactly at the extents

 

      vp.ViewHeight = hgt;

 

      // Set a custom scale to zoom out slightly (could also

      // vp.ViewHeight *= 1.1, for instance)

 

      vp.CustomScale *= fac;

    }

  }

 

  public class Commands

  {

    [CommandMethod("CL")]

    public void CreateLayout()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      if (doc == null)

        return;

      var db = doc.Database;

      var ed = doc.Editor;

 

      var ext = new Extents2d();

 

      using (var tr = db.TransactionManager.StartTransaction())

      {

        // Create and select a new layout tab

 

        var id = LayoutManager.Current.CreateAndMakeLayoutCurrent("NewLayout");

 

        // Open the created layout

 

        var lay = (Layout)tr.GetObject(id, OpenMode.ForWrite);

 

        // Make some settings on the layout and get its extents

 

        lay.SetPlotSettings(

          //"ISO_full_bleed_2A0_(1189.00_x_1682.00_MM)", // Try this big boy!

          "ANSI_B_(11.00_x_17.00_Inches)",

          "monochrome.ctb",

          "DWF6 ePlot.pc3"

        );

 

        ext = lay.GetMaximumExtents();

 

        lay.ApplyToViewport(

          tr, 2,

          vp =>

          {

            // Size the viewport according to the extents calculated when

            // we set the PlotSettings (device, page size, etc.)

            // Use the standard 10% margin around the viewport

            // (found by measuring pixels on screenshots of Layout1, etc.)

 

            vp.ResizeViewport(ext, 0.8);

 

            // Adjust the view so that the model contents fit

            if (ValidDbExtents(db.Extmin, db.Extmax))

            {

              vp.FitContentToViewport(new Extents3d(db.Ex
tmin, db.Extmax), 0.9);

            }

 

            // Finally we lock the view to prevent meddling

 

            vp.Locked = true;

          }

        );

 

        // Commit the transaction

 

        tr.Commit();

      }

 

      // Zoom so that we can see our new layout, again with a little padding

 

      ed.Command("_.ZOOM", "_E");

      ed.Command("_.ZOOM", ".7X");

      ed.Regen();

    }

 

    // Returns whether the provided DB extents - retrieved from

    // Database.Extmin/max - are "valid" or whether they are the default

    // invalid values (where the min's coordinates are positive and the

    // max coordinates are negative)

 

    private bool ValidDbExtents(Point3d min, Point3d max)

    {

      return

        !(min.X > 0 && min.Y > 0 && min.Z > 0 &&

          max.X < 0 && max.Y < 0 && max.Z < 0);

    }

  }

}

 

And here's the CL command – for CreateLayout – in action.

Create Layout

From looking through the code you can see it does pretty much what Thomas was asking for: after creating the layout and setting the page size, plot settings table and a device, we then modify the viewport (after creating it, if viewport creation on layout creation isn't specified in AutoCAD's OPTIONS) to contain the extents of our database – with a little padding so that it's not too tight. After all is done we then zoom to the extents and then with an additional .7X – again, to leave some padding.

All in all it seems to work pretty much as the manual creation process does, the advantage being you can tweak the code for your own purposes, of course. One area I know needs a little work is the extents for 3D viewports: ideally I'd project the model (or just the bounding box, if lazy) along the view direction to the XY plane… right now I'm just lopping off the Z coordinate, which is a bit lazy. I'll try to add that capability in a future update.

Hopefully I haven't missed anything else that's significant: please do post a comment if you spot something.

Update:

Thanks to Parrish Husband for pointing out an opportunity to streamline the code slightly. I went ahead and made the change, and took the opportunity to fix a bug I stumbled across where I tried to fit the viewport to the extents of an empty drawing. I've updated the code above.

12 responses to “Creating an AutoCAD layout with custom plot and viewport settings using .NET”

  1. Sorry for the off topic, but I could not find any other form of contact. Can you hint me how to capture command in drawings (including those newly created) in the AC2015? I noticed that bothered me startup screen. Even If it is turn off, after closing all the drawings appear again startup screen and I can not add to the drawing commands reactor. In earlier versions, there was no such problem.
    Just give me an example of how to properly capture command in any situation, that is when the drawing is created from scratch, opened and closed.

    1. Please post support questions to the AutoCAD .NET forum, in future: forums.autodesk.com/

      You'll need to add event handlers to the DocumentCollection (via Application.DocumentManager) to look for Documents being created and destroyed. You can then add/remove your various Command-related handlers to/from the Documents.

      Kean

  2. Parrish Husband Avatar

    Another great post, really liking the extension methods you've been creating lately. One ultra nitpicky maintenance suggestion I have is on the ApplyToViewport method. You've got the action 'f' being called in two places, one of which may get missed if someone ever comes in to make changes. Here's one way of doing a single call: pastebin.com/AN2gfgjE (the first of which I had wrong)

    1. Great suggestion - thanks for the second pair of eyes. 🙂

      One minor tweak: your code, as it stands, will leave vp set to the last viewport if it doesn't find one with the right number... so I'm keeping vp as the outer variable and using vp2 to open the viewport in the loop... we only set vp = vp2 when we find the right number.

      I'll update the code in the post - thanks again!

      Kean

      1. Parrish Husband Avatar

        Kean, yeah I noticed that right as I posted it (typical hah). I reverted back to your original pattern with the outer variable in my latest PasteBin.

  3. Hi Kean, very useful post. I think there is a little mistake in SetPlotSettings() method. The upgraded variable is never set as true. Could you check?
    Best regards.

    1. Hi Antonio,

      You're absolutely right - I went and fixed the code in the post.

      Thanks!

      Kean

  4. BSB CADD Hyderabad Avatar
    BSB CADD Hyderabad

    Very Nice Information about AutoCAD Layout and Plot settings sekharbabu.in

  5. Decided to speak out because the problem was not obvious. If you place the code in Commands.cs file as it is shown in the example, when the library is loaded an error may occur on communication of the Assembly with the resource file. To resolve this problem you need the class Extensions to come after the class Commands.

  6. Jürgen A. Becker Avatar

    Hi Kean,
    very usefull, but:
    What about when you haven't got the right device. As a developer you haven't got the same devices on your computer. What can we do in such situation?
    Regards Jürgen

    1. Hi Jürgen,

      You can iterate through the available devices and query their properties. Is that a suitable approach?

      Best,

      Kean

      1. Jürgen A. Becker Avatar

        Hi Kean,

        thanks, that is what I'm looking for. Sooooo easy.

        Best Jürgen

Leave a Reply to BSB CADD Hyderabad Cancel reply

Your email address will not be published. Required fields are marked *