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 filename

    )

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

      Database db = doc.Database;

      PreviewEndPlotStatus ret =

        PreviewEndPlotStatus.Cancel;

      ObjectIdCollection layoutsToPlot;

      if (isPreview && layoutNumIfPreview >= 0)

      {

        // Preview is really pre-sheet, so we reduce the

        // sheet collection to contain the one we want

        layoutsToPlot = new ObjectIdCollection();

        layoutsToPlot.Add(

          layoutSet[layoutNumIfPreview]

        );

      }

      else

      {

        // If we're plotting we need all the sheets,

        // so copy the ObjectIds across

        ObjectId[] ids = new ObjectId[layoutSet.Count];

        layoutSet.CopyTo(ids,0);

        layoutsToPlot = new ObjectIdCollection(ids);

      }

      Transaction tr =

        db.TransactionManager.StartTransaction();

      using (tr)

      {

        // Create a Progress Dialog to provide info

        // and allow thej user to cancel

        PlotProgressDialog ppd =

          new PlotProgressDialog(

            isPreview,

            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,

              "DWFx ePlot (XPS Compatible).pc3",

              "ANSI_A_(8.50_x_11.00_Inches)"

            );

            // We need a PlotInfo object

            // linked to the layout

            PlotInfo pi = new PlotInfo();

            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;

            PlotInfoValidator piv =

              new PlotInfoValidator();

            piv.MediaMatchingPolicy =

              MatchingPolicy.MatchEnabled;

            piv.Validate(pi);

            // We set the sheet name per sheet

            ppd.set_PlotMsgString(

              PlotMessageIndex.SheetName,

              doc.Name.Substring(

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

              ) +

              " - " +

              lo.LayoutName

            );

            if (numSheet == 1)

            {

              // All other messages get set once

              ppd.set_PlotMsgString(

                PlotMessageIndex.DialogTitle,

                "Custom Preview 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/preview, 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,

                !isPreview,

                filename

              );

            }

            // Which may contains multiple sheets

            ppd.LowerSheetProgressRange = 0;

            ppd.UpperSheetProgressRange = 100;

            ppd.SheetProgressPos = 0;

            PlotPageInfo ppi = new PlotPageInfo();

            pe.BeginPage(

              ppi,

              pi,

              (numSheet == layoutsToPlot.Count),

              null

            );

            ppd.OnBeginSheet();

            pe.BeginGenerateGraphics(null);

            ppd.SheetProgressPos = 50;

            pe.EndGenerateGraphics(null);

            // Finish the sheet

            PreviewEndPlotInfo pepi =

              new PreviewEndPlotInfo();

            pe.EndPage(pepi);

            ret = pepi.Status;

            ppd.SheetProgressPos = 100;

            ppd.OnEndSheet();

            numSheet++;

            // Update the overall progress

            ppd.PlotProgressPos +=

              (100 / layoutsToPlot.Count);

          }

          // Finish the document

          pe.EndDocument(null);

          // And finish the plot

          ppd.PlotProgressPos = 100;

          ppd.OnEndPlot();

          pe.EndPlot(null);

        }

      }

      return ret;

    }

  }

}

Here's what you see when you run the MPREV command:

Multisheet_plot_preview

Once you select the plot button, the plot gets driven as before:

Multisheet_plot

2 responses to “Previewing and plotting multiple sheets in AutoCAD using .NET”

  1. I have tried a great deal of variation on this code (in VB.net) with adjustment referenced off the .net discussion groups for autodesk.

    I regularly seem to have a problem with the medianame and related entry values.

    The most common erro would be a eNotValidInput exception from lines similar to:
    psv.SetPlotConfigurationName(ps, "DWFx ePlot (XPS Compatible).pc3", "ANSI_A_8.50_x_11.00_Inches)");
    Since that media name does not exist in my network, i need a concise way to find and add an appropriate media to the plotter. Or at least it appears that way.

    can you suggest a way to select or iterate the medias available and how best to connect them to the plotinfovalidator?

    This is the part or the plotsettingsvalidator blow up on me often it seems.

  2. I suspect you a) are not using AutoCAD 2008 or b) have not installed the DWFx driver mentioned in the post, which should allow you to use this device (and I believe the media size).

    I'll put together and post a quick sample showing how to let the user select the device and media via the command-line.

    Regards,

    Kean

  3. Oh, I've just noticed you may have a typo in the media name... "ANSI_A_8.50_x_11.00_Inches)" should be "ANSI_A_(8.50_x_11.00_Inches)".

    Kean

  4. Jim Dowthwaite Avatar
    Jim Dowthwaite

    I'm having the same problem with this article and the simple preview article. If I try to run the routine more than once I get the following: Another plot is in progress.

    What is needed to cancel the current plot?

    The other problem I'm having (with my own code) is AutoCAD will freeze in the preview. Any idea what could be causing this?

    Thanks in advance for any help you can offer,

    Jim

  5. Kean Walmsley Avatar

    Without spending time to investigate this, I suspect it may be due to background plotting. If you disable that, it may help.

    Kean

  6. I'm using ACA 2010, and have some odd behavior. If I run the command while looking at the Model tab, it fails with eLayoutNotCurrent, because the [LayoutManager.Current.CurrentLayout = lo.LayoutName] line won't change it off of model space. If I change to a layout and then back to model space and run the command, it gives and access exception on that line. Do you think there is any way to work around that?

  7. Ok, I think I figured it out. I was running the code from a button that was on a pallette, and that pallete was started with the CommandFlags.Session flag. That apparrently prevents any use of the LayoutManager.Current.CurrentLayout property to change layouts, and alot of memory access exceptions. I changed my button to used the SendStringToExecute function to run a command that calls the code instead.

  8. Kean,

    I know this is an older post and I've used this in the past for the purposes of creating a plotting routine for our work. Thanks for posting these types of things.

    I have a question related to calling netload from the core console and invoking something similar to this - which I've successfully done. My problem is that the core console does not seem to apply the CTB file correctly when I assign it with the PlotSettingsValidator using the SetCurrentStyleSheet method. My files are plotted, but they are as if there was no CTB file specified (i.e. in color and not black/white with the pen weights assigned).

    Have you heard of this? Do you know why the CTB file assignment is having no effect on the plot?

    Thanks,

    Dave

    1. Dave,

      This doesn't ring a bell, but then my knowledge of this is fading, at this stage.

      Please post to the AutoCAD .NET forum: someone there should be able to help.

      Best,

      Kean

Leave a Reply to Jim Dowthwaite Cancel reply

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