Plotting a window from AutoCAD using .NET

This post extends this previous post that dealt with driving a single-sheet AutoCAD plot by adding some code to handle selection and transformation of a window to plot.

First order of business was to allow the user to select the window to plot. For this I used the classic combination of Editor.GetPoint() for the first corner) and Editor.GetCorner() for the second. All well and good, but the points returned by these functions are in UCS (User Coordinate System) coordinates. Which meant that as it stood, the code would work just fine if (and only if) the view we were using to select the window was parallell with the World Coordinate System (and no additional UCS was in place). In order to deal with the very common scenario of either a UCS being used or the current modelspace view being orbited (etc.), we need to transform the current UCS coordinates into DCS, the Display Coordinate System.

Thankfully that's pretty easy: no need for matrices or anything, we simply use another old friend, acedTrans() (the ObjectARX equivalent of the (trans) function, for you LISPers out there). There isn't a direct equivalent for this function in the managed API (that I'm aware of), so we have to use P/Invoke to call the ObjectARX function. The technique is shown in this DevNote, for those of you that have access to the ADN website:

How to call acedTrans from .NET application?

Otherwise the changes to the original code are very minor: we need to set the extents of the window to plot and then set the plot type to "window", apparently in that order. AutoCAD complained when I initially made the calls in the opposite sequence.

Here's the C# code:

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.PlottingServices;

using Autodesk.AutoCAD.Geometry;

using System.Runtime.InteropServices;

using System;


namespace PlottingApplication

{

  public class SimplePlottingCommands

  {

    [DllImport("acad.exe",

              CallingConvention = CallingConvention.Cdecl,

              EntryPoint="acedTrans")

    ]

    static extern int acedTrans(

      double[] point,

      IntPtr fromRb,

      IntPtr toRb,

      int disp,

      double[] result

    );


    [CommandMethod("winplot")]

    static public void WindowPlot()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

      Database db = doc.Database;


      PromptPointOptions ppo =

        new PromptPointOptions(

          "\nSelect first corner of plot area: "

        );

      ppo.AllowNone = false;


      PromptPointResult ppr =

        ed.GetPoint(ppo);


      if (ppr.Status != PromptStatus.OK)

        return;


      Point3d first = ppr.Value;


      PromptCornerOptions pco =

        new PromptCornerOptions(

          "\nSelect second corner of plot area: ",

          first

        );

      ppr = ed.GetCorner(pco);


      if (ppr.Status != PromptStatus.OK)

        return;


      Point3d second = ppr.Value;


      // Transform from UCS to DCS


      ResultBuffer rbFrom =

        new ResultBuffer(new TypedValue(5003, 1)),

                  rbTo =

        new ResultBuffer(new TypedValue(5003, 2));


      double[] firres = new double[] { 0, 0, 0 };

      double[] secres = new double[] { 0, 0, 0 };


      // Transform the first point...


      acedTrans(

        first.ToArray(),

        rbFrom.UnmanagedObject,

        rbTo.UnmanagedObject,

        0,

        firres

      );


      // ... and the second


      acedTrans(

        second.ToArray(),

        rbFrom.UnmanagedObject,

        rbTo.UnmanagedObject,

        0,

        secres

      );


      // We can safely drop the Z-coord at this stage


      Extents2d window =

        new Extents2d(

          firres[0],

          firres[1],

          secres[0],

          secres[1]

        );


      Transaction tr =

        db.TransactionManager.StartTransaction();

      using (tr)

      {

        // We'll be plotting the current layout


        BlockTableRecord btr =

          (BlockTableRecord)tr.GetObject(

            db.CurrentSpaceId,

            OpenMode.ForRead

          );

        Layout lo =

          (Layout)tr.GetObject(

            btr.LayoutId,

            OpenMode.ForRead

          );


        // We need a PlotInfo object

        // linked to the layout


        PlotInfo pi = new PlotInfo();

        pi.Layout = btr.LayoutId;


        // We need a PlotSettings object

        // based on the layout settings

        // which we then customize


        PlotSettings ps =

          new PlotSettings(lo.ModelType);

        ps.CopyFrom(lo);


        // The PlotSettingsValidator helps

        // create a valid PlotSettings object


        PlotSettingsValidator psv =

          PlotSettingsValidator.Current;


        // We'll plot the extents, centered and

        // scaled to fit


        psv.SetPlotWindowArea(ps, window);

        psv.SetPlotType(

          ps,

        Autodesk.AutoCAD.DatabaseServices.PlotType.Window

        );

        psv.SetUseStandardScale(ps, true);

        psv.SetStdScaleType(ps, StdScaleType.ScaleToFit);

        psv.SetPlotCentered(ps, true);


        // We'll use the standard DWF PC3, as

        // for today we're just plotting to file


        psv.SetPlotConfigurationName(

          ps,

          "DWF6 ePlot.pc3",

          "ANSI_A_(8.50_x_11.00_Inches)"

        );


        // We need to link the PlotInfo to the

        // PlotSettings and then validate it


        pi.OverrideSettings = ps;

        PlotInfoValidator piv =

          new PlotInfoValidator();

        piv.MediaMatchingPolicy =

          MatchingPolicy.MatchEnabled;

        piv.Validate(pi);


        // A PlotEngine does the actual plotting

        // (can also create one for Preview)


        if (PlotFactory.ProcessPlotState ==

            ProcessPlotState.NotPlotting)

        {

          PlotEngine pe =

            PlotFactory.CreatePublishEngine();

          using (pe)

          {

            // Create a Progress Dialog to provide info

            // and allow thej user to cancel


            PlotProgressDialog ppd =

              new PlotProgressDialog(false, 1, true);

            using (ppd)

            {

              ppd.set_PlotMsgString(

                PlotMessageIndex.DialogTitle,

                "Custom Plot Progress"

              );

              ppd.set_PlotMsgString(

                PlotMessageIndex.CancelJobButtonMessage,

                "Cancel Job"

              );

              ppd.set_PlotMsgString(

                PlotMessageIndex.CancelSheetButtonMessage,

                "Cancel Sheet"

              );

              ppd.set_PlotMsgString(

                PlotMessageIndex.SheetSetProgressCaption,

                "Sheet Set Progress"

              );

              ppd.set_PlotMsgString(

                PlotMessageIndex.SheetProgressCaption,

                "Sheet Progress"

              );

              ppd.LowerPlotProgressRange = 0;

              ppd.UpperPlotProgressRange = 100;

              ppd.PlotProgressPos = 0;


              // Let's start the plot, at last


              ppd.OnBeginPlot();

              ppd.IsVisible = true;

              pe.BeginPlot(ppd, null);


              // We'll be plotting a single document


              pe.BeginDocument(

                pi,

                doc.Name,

                null,

                1,

                true, // Let's plot to file

                "c:\\test-output"

              );


              // Which contains a single sheet


              ppd.OnBeginSheet();


              ppd.LowerSheetProgressRange = 0;

              ppd.UpperSheetProgressRange = 100;

              ppd.SheetProgressPos = 0;


              PlotPageInfo ppi = new PlotPageInfo();

              pe.BeginPage(

                ppi,

                pi,

                true,

                null

              );

              pe.BeginGenerateGraphics(null);

              pe.EndGenerateGraphics(null);


              // Finish the sheet

              pe.EndPage(null);

              ppd.SheetProgressPos = 100;

              ppd.OnEndSheet();


              // Finish the document


              pe.EndDocument(null);


              // And finish the plot


              ppd.PlotProgressPos = 100;

              ppd.OnEndPlot();

              pe.EndPlot(null);

            }

          }

        }

        else

        {

          ed.WriteMessage(

            "\nAnother plot is in progress."

          );

        }

      }

    }

  }

}

October 11, 2007 in AutoCAD, AutoCAD .NET, Plotting | Permalink | Comments (26) | TrackBack

Allowing selection of an AutoCAD plot device and media name using .NET

A comment came in on this previous post regarding how best to know whether a media name is valid during your plot configuration.

There are a few approaches, other than the one I chose of hardcoding the device and media names. The first is to implement a user interface of some kind which allows the user to select the device and media names. Another approach for setting the media name is to use PlotSettingsValidator.SetClosestMediaName() to choose the media name that most closely matches the paper size you desire.

Today I'll focus on the first option, although I'm only going to implement a basic, command-line user interface. It should be straightforward to extend this to implement a WinForm with comboboxes for the device and media names.

I implemented a simple function called ChooseDeviceAndMedia() which queries the current device list, allows selection from it, and then gets the media name list and allows selection from that. I chose not to worry about displaying locale-specific names, for now - I'll leave that for a future post (let me know if you're interested :-).

Here's the function integrated into the C# code from this previous post, which defines a command called MPLOT:

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.PlottingServices;

using System.Collections.Specialized;


namespace PlottingApplication

{

  public class PlottingCommands

  {

    static public string[] ChooseDeviceAndMedia()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;


      // Assign default return values


      string devname = "", medname = "";


      PlotSettingsValidator psv =

        PlotSettingsValidator.Current;


      // Let's first select the device


      StringCollection devlist =

        psv.GetPlotDeviceList();


      for (int i = 0; i < devlist.Count; i++)

      {

        ed.WriteMessage(

          "\n{0} {1}",

          i + 1,

          devlist[i]

        );

      }

      PromptIntegerOptions opts =

        new PromptIntegerOptions(

          "\nEnter number of device to select: "

        );

      opts.LowerLimit = 1;

      opts.UpperLimit = devlist.Count;


      PromptIntegerResult pir =

        ed.GetInteger(opts);

      if (pir.Status == PromptStatus.OK)

      {

        devname = devlist[pir.Value - 1];

        ed.WriteMessage(

          "\nSelected: {0}\n",

          devname

        );


        // Now let's select the media


        PlotSettings ps = new PlotSettings(true);

        using (ps)

        {

          // We should refresh the lists,

          // in case setting the device impacts

          // the available media


          psv.SetPlotConfigurationName(

            ps,

            devname,

            null

          );

          psv.RefreshLists(ps);


          StringCollection medlist =

            psv.GetCanonicalMediaNameList(ps);


          for (int i = 0; i < medlist.Count; i++)

          {

            ed.WriteMessage(

              "\n{0} {1}",

              i + 1,

              medlist[i]

            );

          }

          opts.Message =

            "\nEnter number of media to select: ";

          opts.LowerLimit = 1;

          opts.UpperLimit = medlist.Count;


          pir = ed.GetInteger(opts);

          if (pir.Status == PromptStatus.OK)

          {

            medname = medlist[pir.Value - 1];

            ed.WriteMessage(

              "\nSelected: {0}\n",

              medname

            );

          }

        }

      }

      return new string[2] { devname, medname };

    }


    [CommandMethod("mplot")]

    static public void MultiSheetPlot()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

      Database db = doc.Database;


      Transaction tr =

        db.TransactionManager.StartTransaction();

      using (tr)

      {

        BlockTable bt =

          (BlockTable)tr.GetObject(

            db.BlockTableId,

            OpenMode.ForRead

          );


        PlotInfo pi = new PlotInfo();

        PlotInfoValidator piv =

          new PlotInfoValidator();

        piv.MediaMatchingPolicy =

          MatchingPolicy.MatchEnabled;


        // A PlotEngine does the actual plotting

        // (can also create one for Preview)


        if (PlotFactory.ProcessPlotState ==

            ProcessPlotState.NotPlotting)

        {

          string[] devmed = ChooseDeviceAndMedia();


          // Only proceed if we have values for both


          if (devmed[0] != "" && devmed[1] != "")

          {

            string devname = devmed[0];

            string medname = devmed[1];


            PlotEngine pe =

              PlotFactory.CreatePublishEngine();

            using (pe)

            {

              // Collect all the paperspace layouts

              // for plotting


              ObjectIdCollection layoutsToPlot =

                new ObjectIdCollection();


              foreach (ObjectId btrId in bt)

              {

                BlockTableRecord btr =

                  (BlockTableRecord)tr.GetObject(

                    btrId,

                    OpenMode.ForRead

                  );

                if (btr.IsLayout &&

                    btr.Name.ToUpper() !=

                      BlockTableRecord.ModelSpace.ToUpper())

                {

                  layoutsToPlot.Add(btrId);

                }

              }


              // Create a Progress Dialog to provide info

              // and allow thej user to cancel


              PlotProgressDialog ppd =

                new PlotProgressDialog(

                  false,

                  layoutsToPlot.Count,

                  true

                );

              using (ppd)

              {

                int numSheet = 1;


                foreach (ObjectId btrId in layoutsToPlot)

                {

                  BlockTableRecord btr =

                    (BlockTableRecord)tr.GetObject(

                      btrId,

                      OpenMode.ForRead

                    );

                  Layout lo =

                    (Layout)tr.GetObject(

                      btr.LayoutId,

                      OpenMode.ForRead

                    );


                  // We need a PlotSettings object

                  // based on the layout settings

                  // which we then customize


                  PlotSettings ps =

                    new PlotSettings(lo.ModelType);

                  ps.CopyFrom(lo);


                  // The PlotSettingsValidator helps

                  // create a valid PlotSettings object


                  PlotSettingsValidator psv =

                    PlotSettingsValidator.Current;


                  // We'll plot the extents, centered and

                  // scaled to fit


                  psv.SetPlotType(

                    ps,

                  Autodesk.AutoCAD.DatabaseServices.PlotType.Extents

                  );

                  psv.SetUseStandardScale(ps, true);

                  psv.SetStdScaleType(ps, StdScaleType.ScaleToFit);

                  psv.SetPlotCentered(ps, true);


                  // We'll use the standard DWFx PC3, as

                  // this supports multiple sheets


                  psv.SetPlotConfigurationName(

                    ps,

                    devname,

                    medname

                  );


                  // We need a PlotInfo object

                  // linked to the layout


                  pi.Layout = btr.LayoutId;


                  // Make the layout we're plotting current


                  LayoutManager.Current.CurrentLayout =

                    lo.LayoutName;


                  // We need to link the PlotInfo to the

                  // PlotSettings and then validate it


                  pi.OverrideSettings = ps;

                  piv.Validate(pi);


                  if (numSheet == 1)

                  {

                    ppd.set_PlotMsgString(

                      PlotMessageIndex.DialogTitle,

                      "Custom Plot Progress"

                    );

                    ppd.set_PlotMsgString(

                      PlotMessageIndex.CancelJobButtonMessage,

                      "Cancel Job"

                    );

                    ppd.set_PlotMsgString(

                      PlotMessageIndex.CancelSheetButtonMessage,

                      "Cancel Sheet"

                    );

                    ppd.set_PlotMsgString(

                      PlotMessageIndex.SheetSetProgressCaption,

                      "Sheet Set Progress"

                    );

                    ppd.set_PlotMsgString(

                      PlotMessageIndex.SheetProgressCaption,

                      "Sheet Progress"

                    );

                    ppd.LowerPlotProgressRange = 0;

                    ppd.UpperPlotProgressRange = 100;

                    ppd.PlotProgressPos = 0;


                    // Let's start the plot, at last


                    ppd.OnBeginPlot();

                    ppd.IsVisible = true;

                    pe.BeginPlot(ppd, null);


                    // We'll be plotting a single document


                    pe.BeginDocument(

                      pi,

                      doc.Name,

                      null,

                      1,

                      true, // Let's plot to file

                      "c:\\test-multi-sheet"

                    );

                  }


                  // Which may contains multiple sheets


                  ppd.set_PlotMsgString(

                    PlotMessageIndex.SheetName,

                    doc.Name.Substring(

                      doc.Name.LastIndexOf("\\") + 1

                    ) +

                    " - sheet " + numSheet.ToString() +

                    " of " + layoutsToPlot.Count.ToString()

                  );


                  ppd.OnBeginSheet();


                  ppd.LowerSheetProgressRange = 0;

                  ppd.UpperSheetProgressRange = 100;

                  ppd.SheetProgressPos = 0;


                  PlotPageInfo ppi = new PlotPageInfo();

                  pe.BeginPage(

                    ppi,

                    pi,

                    (numSheet == layoutsToPlot.Count),

                    null

                  );

                  pe.BeginGenerateGraphics(null);

                  ppd.SheetProgressPos = 50;

                  pe.EndGenerateGraphics(null);


                  // Finish the sheet

                  pe.EndPage(null);

                  ppd.SheetProgressPos = 100;

                  ppd.OnEndSheet();

                  numSheet++;

                  ppd.PlotProgressPos +=

                    (100 / layoutsToPlot.Count);

                }


                // Finish the document


                pe.EndDocument(null);


                // And finish the plot


                ppd.PlotProgressPos = 100;

                ppd.OnEndPlot();

                pe.EndPlot(null);

              }

            }

          }

        }

        else

        {

          ed.WriteMessage(

            "\nAnother plot is in progress."

          );

        }

      }

    }

  }

}

Here's what I see at the command-line when I run the MPLOT command on my system (items 7-18 below are network printers in various Autodesk offices):

Command: MPLOT


1 None

2 SnagIt 8

3 PDF-XChange 3.0

4 Microsoft XPS Document Writer

5 Microsoft Office Live Meeting Document Writer

6 Adobe PDF

7 \\adscctps1\cctprn000

8 \\adsneups2\neuprn304

9 \\adsneups2\neuprn306

10 \\adsneups2\neuprn307

11 \\adsneups2\neuprn317

12 \\banhpa001\banprn001

13 \\beihpa001\BEIPRN002

14 \\beihpa001\BEIPRN003

15 \\farhpa001\FARPRN101

16 \\pra-pc61237\Xerox WorkCentre Pro 35 PCL6

17 \\tokhpa001\tokprn401

18 \\tokhpa001\tokprn402

19 Default Windows System Printer.pc3

20 DWF6 ePlot.pc3

21 DWFx ePlot (XPS Compatible).pc3

22 DWG To PDF.pc3

23 PublishToWeb JPG.pc3

24 PublishToWeb PNG.pc3

Enter number of device to select: 22


Selected: DWG To PDF.pc3


1 ISO_expand_A0_(841.00_x_1189.00_MM)

2 ISO_A0_(841.00_x_1189.00_MM)

3 ISO_expand_A1_(841.00_x_594.00_MM)

4 ISO_expand_A1_(594.00_x_841.00_MM)

5 ISO_A1_(841.00_x_594.00_MM)

6 ISO_A1_(594.00_x_841.00_MM)

7 ISO_expand_A2_(594.00_x_420.00_MM)

8 ISO_expand_A2_(420.00_x_594.00_MM)

9 ISO_A2_(594.00_x_420.00_MM)

10 ISO_A2_(420.00_x_594.00_MM)

11 ISO_expand_A3_(420.00_x_297.00_MM)

12 ISO_expand_A3_(297.00_x_420.00_MM)

13 ISO_A3_(420.00_x_297.00_MM)

14 ISO_A3_(297.00_x_420.00_MM)

15 ISO_expand_A4_(297.00_x_210.00_MM)

16 ISO_expand_A4_(210.00_x_297.00_MM)

17 ISO_A4_(297.00_x_210.00_MM)

18 ISO_A4_(210.00_x_297.00_MM)

19 ARCH_expand_E1_(30.00_x_42.00_Inches)

20 ARCH_E1_(30.00_x_42.00_Inches)

21 ARCH_expand_E_(36.00_x_48.00_Inches)

22 ARCH_E_(36.00_x_48.00_Inches)

23 ARCH_expand_D_(36.00_x_24.00_Inches)

24 ARCH_expand_D_(24.00_x_36.00_Inches)

25 ARCH_D_(36.00_x_24.00_Inches)

26 ARCH_D_(24.00_x_36.00_Inches)

27 ARCH_expand_C_(24.00_x_18.00_Inches)

28 ARCH_expand_C_(18.00_x_24.00_Inches)

29 ARCH_C_(24.00_x_18.00_Inches)

30 ARCH_C_(18.00_x_24.00_Inches)

31 ANSI_expand_E_(34.00_x_44.00_Inches)

32 ANSI_E_(34.00_x_44.00_Inches)

33 ANSI_expand_D_(34.00_x_22.00_Inches)

34 ANSI_expand_D_(22.00_x_34.00_Inches)

35 ANSI_D_(34.00_x_22.00_Inches)

36 ANSI_D_(22.00_x_34.00_Inches)

37 ANSI_expand_C_(22.00_x_17.00_Inches)

38 ANSI_expand_C_(17.00_x_22.00_Inches)

39 ANSI_C_(22.00_x_17.00_Inches)

40 ANSI_C_(17.00_x_22.00_Inches)

41 ANSI_expand_B_(17.00_x_11.00_Inches)

42 ANSI_expand_B_(11.00_x_17.00_Inches)

43 ANSI_B_(17.00_x_11.00_Inches)

44 ANSI_B_(11.00_x_17.00_Inches)

45 ANSI_expand_A_(11.00_x_8.50_Inches)

46 ANSI_expand_A_(8.50_x_11.00_Inches)

47 ANSI_A_(11.00_x_8.50_Inches)

48 ANSI_A_(8.50_x_11.00_Inches)

Enter number of media to select: 35


Selected: ANSI_D_(34.00_x_22.00_Inches)

Effective plotting area:  15.29 wide by 20.60 high

Effective plotting area:  13.29 wide by 17.31 high


Plotting viewport 2.


Plotting viewport 1.

Regenerating layout.

Regenerating model.

Effective plotting area:  15.69 wide by 20.60 high

Effective plotting area:  15.69 wide by 20.59 high


Plotting viewport 2.


Plotting viewport 1.

Regenerating layout.

Regenerating model.

Effective plotting area:  15.69 wide by 20.60 high

Effective plotting area:  15.69 wide by 20.59 high


Plotting viewport 2.


Plotting viewport 1.

Regenerating layout.

Regenerating model.

Effective plotting area:  15.69 wide by 20.60 high

Effective plotting area:  14.07 wide by 18.34 high


Plotting viewport 2.


Plotting viewport 1.

Regenerating layout.

Regenerating model.

October 9, 2007 in AutoCAD, AutoCAD .NET, Plotting, User interface | Permalink | Comments (2) | TrackBack

Previewing and plotting multiple sheets in AutoCAD using .NET

This was a fun one to work on. The code in this post combines and extends upon techniques shown in two earlier posts: one showing how to plot multiple sheets and the other showing how to preview a single-sheet plot.

One of the key differences when plotting or previewing is that while plotting can directly support multiple sheets (assuming the device does so), previewing does not. The good news is that AutoCAD provides you the user interface elements to allow cycling through plots: the user is provided with "Next" and "Previous" buttons - it's then up to you to implement the appropriate logic to preview different sheets when the buttons are used.

I chose to use the same helper function for both preview and plot, even though they are a little different in nature. The reason is obvious (to me, at least) - it reduces the amount of code to debug and maintain - but it might, for some, make the code a little less easy to read. Ultimately the trick I used was to reduce the set of sheets being handled at the beginning of the function to a single sheet in the case of a preview, which allowed me to combine both approaches in a single function.

Here's the C# code:

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.PlottingServices;

using System.Collections.Generic;


namespace PlottingApplication

{

  public class PreviewCommands

  {

    [CommandMethod("mprev")]

    static public void MultiSheetPreview()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

      Database db = doc.Database;


      ObjectIdCollection layoutsToPlot =

        new ObjectIdCollection();


      Transaction tr =

        db.TransactionManager.StartTransaction();

      using (tr)

      {

        // First we need to collect the layouts to

        // plot/preview in tab order


        SortedDictionary<int, ObjectId> layoutDict =

          new SortedDictionary<int, ObjectId>();


        BlockTable bt =

          (BlockTable)tr.GetObject(

            db.BlockTableId,

            OpenMode.ForRead

          );


        foreach (ObjectId btrId in bt)

        {

          BlockTableRecord btr =

            (BlockTableRecord)tr.GetObject(

              btrId,

              OpenMode.ForRead

            );

          if (btr.IsLayout &&

              btr.Name.ToUpper() !=

                BlockTableRecord.ModelSpace.ToUpper())

          {

            // The dictionary we're using will

            // sort on the tab order of the layout


            Layout lo =

              (Layout)tr.GetObject(

                btr.LayoutId,

                OpenMode.ForRead

              );

            layoutDict.Add(lo.TabOrder,btrId);

          }

        }


        // Let's now get the layout IDs and add them to a

        // standard ObjectIdCollection


        SortedDictionary<int,ObjectId>.ValueCollection vc =

          layoutDict.Values;

        foreach (ObjectId id in vc)

        {

          layoutsToPlot.Add(id);

        }


        // Committing is cheaper than aborting


        tr.Commit();

      }


      // PlotEngines do the previewing and plotting


      if (PlotFactory.ProcessPlotState ==

          ProcessPlotState.NotPlotting)

      {

        int layoutNum = 0;

        bool isFinished = false;

        bool isReadyForPlot = false;


        while (!isFinished)

        {

          // Create the preview engine with the appropriate

          // buttons enabled - this depends on which

          // layout in the list is being previewed


          PreviewEngineFlags flags =

            PreviewEngineFlags.Plot;

          if (layoutNum > 0)

            flags |= PreviewEngineFlags.PreviousSheet;

          if (layoutNum < layoutsToPlot.Count - 1)

            flags |= PreviewEngineFlags.NextSheet;


          PlotEngine pre =

            PlotFactory.CreatePreviewEngine((int)flags);

          using (pre)

          {

            PreviewEndPlotStatus stat =

              MultiplePlotOrPreview(

                pre,

                true,

                layoutsToPlot,

                layoutNum,

                ""

              );


            // We're not checking the list bounds for

            // next/previous as the buttons are only shown

            // when they can be used


            if (stat == PreviewEndPlotStatus.Next)

            {

              layoutNum++;

            }

            else if (stat == PreviewEndPlotStatus.Previous)

            {

              layoutNum--;

            }

            else if (stat == PreviewEndPlotStatus.Normal ||

                    stat == PreviewEndPlotStatus.Cancel)

            {

              isFinished = true;

            }

            else if (stat == PreviewEndPlotStatus.Plot)

            {

              isFinished = true;

              isReadyForPlot = true;

            }

          }

        }


        // If the plot button was used to exit the preview...


        if (isReadyForPlot)

        {

          PlotEngine ple =

            PlotFactory.CreatePublishEngine();

          using (ple)

          {

            PreviewEndPlotStatus stat =

              MultiplePlotOrPreview(

                ple,

                false,

                layoutsToPlot,

                -1,

                "c:\\multisheet-previewed-plot"

              );

          }

        }

      }

      else

      {

        ed.WriteMessage(

          "\nAnother plot is in progress."

        );

      }

    }


    static PreviewEndPlotStatus MultiplePlotOrPreview(

      PlotEngine pe,

      bool isPreview,

      ObjectIdCollection layoutSet,

      int layoutNumIfPreview,

      string file