Monday, 1 February 2016

Map-Plot - batch plotting

If you use Map plot module (Industry Model extension) you can generate maps in a batch process.
Unfortunately we always get an unspecific error message when executing the batch plot module - something along the lines of 

"Plot was not configured correctly"

Funny though as you can generate the preview and plot the preview without any issue.

To get our 400+ maps plotted I wrote a quick'ndirty batch plot plugin myself. It does its job - maybe it is useful for someone else. Most bits are taken from Map development sample or through-the-interface blog. I also copied some bits from another older plugin - that's why a class is called "Inselplan" but has nothing to with it.

Some notes:

- on one PC generating a PLT file took roughly 1 minute per map, on a second PC roughly 5 minutes per map. Both PCs have nearly the same specs and software installed. 
- there is an issue with FDO hatches - if the viewport is rotated some hatches (it seems most hatches except solid) have artifacts (such as a white rectangular or hatches occuring twice but slightly shifted).


Hatch issue - FDO hatches, viewport rotated
Map 2013, SP2

source code:


 using System;  
 using Autodesk.Map.IM.Forms;  
 using Autodesk.Map.IM.Plot.Application;  
 using Autodesk.Map.IM.Plot.Components.PlotLibrary;  
 using Autodesk.Map.IM.Plot.Components.PlotLibrary.ContextMenu;  
 using Autodesk.Map.IM.Plot.Data;  
 using Autodesk.Map.IM.Plot.Components.PlotLibrary.Command;  
 namespace Inselplan_Batch  
 {  
   /// <summary>  
   /// <para>  
   /// Our document plugin - its priority has to be set so it will be loaded after the main plot  
   /// plugin - see this example's TBP file.   
   /// </para>  
   /// <para>  
   /// </para>  
   /// </summary>  
   public class DocumentPlugin : DocumentPlugIn  
   {  
     /// <summary>  
     /// Called by the TB Forms framework - we will use it to initialize our plugin  
     /// </summary>  
     /// <param name="sender"></param>  
     /// <param name="e"></param>  
     public override void OnInitComplete(object sender, EventArgs e)  
     {  
       base.OnInitComplete(sender, e);  
       //get the [PlotFlyIn] for our TB document...  
       PlotFlyIn currentPlotFlyIn = PlotFlyIn.GetCurrent(this.Document);  
       //...which can be [null], if it has been excluded in TB Admin  
       if (null != currentPlotFlyIn)  
       {  
         //connect to the [PlotExtensionInitialized] event - it will be raied by the respective  
         //flyin after the library (TreeView) has been fully loaded in response to  
         //the [WorkspaceOpened] event - now the Plot API can be used  
         currentPlotFlyIn.PlotExtensionInitialized += this.HandlePlotExtensionInitialized;  
       }  
     }  
     private void HandlePlotExtensionInitialized(object sender, EventArgs e)  
     {  
       //get the [PlotFlyIn]...  
       PlotFlyIn plotFlyIn = (PlotFlyIn)sender;  
       //...and the PltPlotLibrary (which is a TreeView)...  
       PltPlotLibrary plotLibrary = plotFlyIn.PltPlotLibrary;  
       //...so we can add our menu entries to the the plot-specific context menu via the library's [PltContextMenuFactory]  
       PltContextMenuFactory contextMenuFactory = plotLibrary.ContextMenuFactory;  
       //That is, it won't be available for templates or groups  
       //context menus are available for the following types:  
       // - IPlot  
       // - IPlotTemplate  
       // - IPlotGroup  
       // - IPlotTemplateGroup  
       // nicht auf Template setzen, der neue Plot wird in der Gruppe gespeichert in der Befehl aufgerufen wird  
       // - das geht aber nicht bei Templates und führt zum Absturz  
       PltCommonContextMenuStrip contextMenuStrip = contextMenuFactory.GetMenuForLibraryType(typeof(IPlotGroup));  
       //every command has its key - the same command (class) key is not allowed to be added  
       //twice to the same context menu  
       string editCommandKey = PltCommand.GetDefaultCommandKey(typeof(PltEditCommand));  
       //the plot context menu contains the [PltEditCommand] command. Get its  
       //insertion index...  
       int editCommandIndex = contextMenuStrip.Items.IndexOfKey(editCommandKey);  
       //...so we can then add our own commands right behind it  
       contextMenuStrip.InsertMenuItem(new Inselplanerstellung(plotLibrary), editCommandIndex + 1);  
       //contextMenuStrip.InsertMenuItem(new MyFastEditCommand2(plotLibrary), editCommandIndex + 2);  
       //alternatively, one can also use the [AddMenuItem] method which adds the command  
       //as the last item - retrieving another command's key + index first is not required then  
       //  
       //contextMenuStrip.AddMenuItem(new MyFastEditCommand2(this.Document, plotLibrary));  
     }  
   }  
 }  
 
 using Inselplan_Batch.Properties;  
 using Autodesk.Map.IM.Plot.Api;  
 using Autodesk.Map.IM.Plot.Components.PlotLibrary;  
 using Autodesk.Map.IM.Plot.Components.PlotLibrary.Command;  
 using Autodesk.Map.IM.Plot.Components.Runtime;  
 using Autodesk.Map.IM.Plot.Components;  
 using Autodesk.Map.IM.Plot.Data;  
 using Autodesk.Map.IM.Data;  
 using Autodesk.Map.IM.PlaneGeometry;  
 using System.Windows.Forms;  
 using System.Collections;  
 using System.Collections.Generic;  
 using Autodesk.AutoCAD.Runtime;  
 using Autodesk.AutoCAD.EditorInput;  
 using Autodesk.AutoCAD.ApplicationServices;  
 using Autodesk.AutoCAD.DatabaseServices;  
 using Autodesk.AutoCAD.PlottingServices;  
 namespace Inselplan_Batch  
 {  
   /// <summary>  
   /// </summary>  
   /// <remarks>  
   /// </remarks>  
   [PltPrerequisiteSingleNodeSelected]  
   public class Inselplanerstellung : PltCommand  
   {  
     /// <summary>  
     /// Ctor - it requires the caller to pass a valid <see cref="PltPlotLibrary"/> instance.  
     /// </summary>  
     /// <param name="library"></param>  
     public Inselplanerstellung(PltPlotLibrary library)  
       : base(library, Resources.command)  
     {  
     }  
     /// <summary>  
     /// Called by the Plot API after the user has selected our context menu entry.  
     /// </summary>  
     protected override void ExecuteCommand()  
     {  
       try  
       {  
         //welche gruppe wurde ausgewhlt?  
         IPlotLibraryGroup groupP = this.GetGroup(this.GetFirstTreeNode(this.PlotLibrary.SelectedItems));  
         //PlotLibrary ist die grafische Repräsentation - das Plot-Flyin/Baumstruktur  
         IPlotConnection plotConnection = this.PlotLibrary.Connection;  
         IPlotLibrary myLibrary = plotConnection.Library;  
         //PltPlotLibrary myPltPlotLibrary = this.PlotLibrary;  
         // Zum Speichern der neuen Plots wird die gruppe verwendet,  
         // aus der der Befehl heraus aufgerufen wurde.  
         //Gruppe also vorher manuell anlegen  
         IPlotLibraryGroup group = this.GetGroup(this.GetFirstTreeNode(this.PlotLibrary.SelectedItems));  
         IPlotGroup plotGroup = group as IPlotGroup;  
         //wird benötigt zum Pdate von PLT_PLOT  
         TBConnection myConnection = plotConnection.Connection;  
         //die beiden Views  
         FeatureClass tabPLT_PLOT_GROUP_PLOT;  
         string tabName_PLT_Plot_GROUP_PLOT = "PLT_PLOT_GROUP_PLOT";  
         string PlotTabelle = "PLT_PLOT";  
         Autodesk.Map.IM.Data.FeatureClass PlotFeatureClass = myConnection.FeatureClasses[PlotTabelle];  
         tabPLT_PLOT_GROUP_PLOT = myConnection.FeatureClasses[tabName_PLT_Plot_GROUP_PLOT];  
          //FID der Plotgruppe?  
         string SQLAbfrage = "select fid from plt_plot_group where group_name = '" + groupP.Name + "'";  
         //this.Application.MessageBox(SQLAbfrage, "Bericht - Hinweis1");  
         Autodesk.Map.IM.Data.Tools.RecordReader reader = new Autodesk.Map.IM.Data.Tools.RecordReader(myConnection);  
         reader.Open(SQLAbfrage);  
         reader.Read();  
         long? FIDPlotGruppe = reader.LngValue("FID");  
         reader.Close();  
         //FID der Plotgruppe, in die geplottet Plots verschoben werden sollen          
         string SQLAbfrage2 = "select fid from plt_plot_group where group_name = 'Grundbuchpläne_geplottet'";  
         //this.Application.MessageBox(SQLAbfrage, "Bericht - Hinweis1");  
         Autodesk.Map.IM.Data.Tools.RecordReader reader2 = new Autodesk.Map.IM.Data.Tools.RecordReader(myConnection);  
         reader.Open(SQLAbfrage2);  
         reader.Read();  
         long? FIDPlotGruppeVerschieben = reader.LngValue("FID");  
         reader.Close();  
         if (FIDPlotGruppe == null)  
         {  
           MessageBox.Show("Zu Plotgruppe '" + groupP.Name + "' keine FID gefunden!");  
         }  
         DialogResult result1 = MessageBox.Show("Achtung: Job Status richtig gesetzt?\nBatchPlot für Gruppe: '" + groupP.Name + "' (FID=" + FIDPlotGruppe+")?",  
         "BatchPlot starten?",  
         MessageBoxButtons.YesNo);  
         if (result1 != DialogResult.Yes)  
           return;  
         // alle Plots durchgehen  
         foreach (Feature feature in tabPLT_PLOT_GROUP_PLOT.GetFeatures(true))  
         {  
           long FIDPlot = feature.Attributes.Get("FID_PLOT").ValueLong;  
           long FIDPlotGroup = feature.Attributes.Get("FID_PLOT_GROUP").ValueLong;  
           //Gruppe "Test"  
           if (FIDPlotGroup != FIDPlotGruppe)  
             continue;  
           IPlot plot = myLibrary.FindPlot(FIDPlot);  
           PltPlotRenderer r = PltPlotRendererFactory.CreateRenderer(RendererConfiguration.Plot, plot, RenderingCompatibility.CurrentVersion);  
           r.Render();  
           bool plotResult = SimplePlot();  
           // DWG schliessen  
           Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.CloseAndDiscard();  
           //Plot in anderen Ordner verschieben  
           if (FIDPlotGruppeVerschieben != null)  
           {  
             Autodesk.Map.IM.Data.Tools.RecordWriter writer = new Autodesk.Map.IM.Data.Tools.RecordWriter(myConnection, "plt_plot_group_plot");  
             writer.Clear();  
             // Key - wird als WHERE Clause für das Update verwendet (...where user_id = userid)  
             writer.WriteKey("fid_plot", FIDPlot);  
             // neuer Werte  
             long lFID = (long)FIDPlotGruppeVerschieben;  
             writer.Write("fid_plot_group", lFID);  
             // Update ausführen  
             writer.Update();  
           }  
         }  
       }  
       catch (ConnectionInvalidatedException ex)  
       {  
         MessageBox.Show("Plot Connection ungültigt. \n" + ex.Message.ToString());  
       }  
     }  
      public bool SimplePlot()  
     {  
       Document doc =  
        Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;  
       Editor ed = doc.Editor;  
       Database db = doc.Database;  
       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.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 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:\\temp\\"+lo.LayoutName  
               );  
               // 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);  
             }  
           }            
           return true;  
         }  
         else  
         {  
           ed.WriteMessage(  
            "\nAnother plot is in progress."  
           );  
           return false;  
         }  
       }  
     }  
   }  
 }