The next project lets your users select layouts from an external drawing not currently opened in AutoCAD, and then publish those layouts as a multisheet PDF file.
How do we publish layouts that exist in drawings that are not open in the current drawing session? You might recall back in the Controlling the drawing environment (Should know) recipe, when we cloned a layout from the current drawing into a side database, and from there we saved the layout to a template drawing. Now, we have a slight variation on that theme: when the user selects the external drawing, the program reads it into a temporary database, determines the number of layouts, and adds the layout names to the dialog box (also slightly modified from its predecessor in the Controlling the drawing environment (Should know) recipe). The user selects the desired layouts and the custom program tells AutoCAD to publish them using AutoCAD's Publisher API. Similar to the example in Controlling the drawing environment (Should know), we will create a small dialog box interface using Microsoft's Windows Presentation Foundation (WPF) (more about this in a moment).
First, we'll add statements for AutoCAD's plotting service and Windows forms:
using System; using System.IO; using System.Text; using System.Windows.Forms; using Autodesk.AutoCAD.Runtime; using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.Geometry; using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.PlottingServices; using AcadApplication = Autodesk.AutoCAD.ApplicationServices.Application; // This line is not mandatory, but improves loading performances [assembly: CommandClass(typeof(Ch7AcadPlugin.MyCommands))] namespace Ch7AcadPlugin { public class MyCommands { //Command to publish selected layouts //in an external dwg to a pdf file [CommandMethod("LOPUB")] public void PubSelLo() { ShowDialog(); } //Display dialog box to specify external drawing, //pdf file, and layouts to publish public void ShowDialog() { Window1 mw = new Window1(); AcadApplication.ShowModalWindow(mw); }
Once the user clicks on OK, we are almost ready to publish. First, we must set the AutoCAD system variable
BackgroundPlot
to0
, so AutoCAD will plot in the foreground. Then, we will get the filename and path of the external drawing://Publish layouts selected from an external drawing to a pdf file public static void PubLo2Pdf() { //Get the active document Document oDoc = AcadApplication.DocumentManager.MdiActiveDocument; Editor ed = oDoc.Editor; Database db = oDoc.Database; //Set BackgroundPlot system variable to 0 so AutoCAD //will plot in the foreground short bkgdPlt = (short)AcadApplication.GetSystemVariable( "BACKGROUNDPLOT"); AcadApplication.SetSystemVariable("BACKGROUNDPLOT", 0); //Get drawing prefix string dwgPath = Path.GetDirectoryName(Window1.txtDwgToRead) + "\\"; //Get drawing file name w/extension string dwgFname = Path.GetFileName(Window1.txtDwgToRead); //Drawing file name w/o extension string docName = Path.GetFileNameWithoutExtension(dwgFname);
Rather than publishing the layouts from the side database, we will add the drawing and layout information to a temporary Drawing Set Description (DSD) file. A DSD file contains the information that AutoCAD needs for the sheet we will publish: in other words, the external drawing and layout details are as follows:
//Begin transaction using (Transaction trans = db.TransactionManager.StartTransaction()) { try { //Create a temporary Dsd file, populated with sheet info for //the specified external drawing and layout(s) to publish DsdEntryCollection dColl = new DsdEntryCollection(); //Add information for each selected layout to the dsd entry foreach (string layoutName in Window1.lstLayoutNames) { DsdEntry dEntry = new DsdEntry(); dEntry.DwgName = dwgPath + dwgFname; dEntry.Layout = layoutName; dEntry.Title = docName + "-" + layoutName; dEntry.NpsSourceDwg = dEntry.DwgName; dColl.Add(dEntry); }
Once we have specified the sheet contents, do the same for the location of the PDF and DSD files. If a PDF file by that name already exists in that location, delete it:
//Set location for multi-sheet pdf and temporary Dsd file DsdData dData = new DsdData(); dData.SheetType = SheetType.MultiPdf; string pdfFname = Path.GetFileName(Window1.txtSavPdf); string pdfName = Path.GetFileNameWithoutExtension(pdfFname); dData.ProjectPath = Path.GetDirectoryName(Window1.txtSavPdf) + "\\"; dData.LogFilePath = Path.GetTempPath() + pdfName + ".log"; dData.NoOfCopies = 1; dData.DestinationName = dData.ProjectPath + pdfName + ".pdf"; //If pdf file with same name/path already exists, delete it File.Delete(dData.DestinationName); dData.SetDsdEntryCollection(dColl); string dFile = Path.GetTempPath()+ pdfName + ".dsd"; dData.WriteDsd(dFile);
When AutoCAD creates a DSD file, it includes a setting to prompt the user for the name of a DWF output file. Since we are publishing a PDF and not a DWF, and since we already specified the PDF location in the dialog box, the prompt is not necessary. To alter the setting, thereby suppressing the prompt command window, we must overwrite it in the DSD file:
//Ensure that the "PromptForDwfName" setting in the dsd file is //suppressed when AutoCAD reads the file. string str = File.ReadAllText(dFile, Encoding.Default); //Overwrite TRUE setting with FALSE to suppress prompt //in AutoCAD str = str.Replace("PromptForDwfName=TRUE", "PromptForDwfName=FALSE"); File.WriteAllText(dFile, str, Encoding.Default); dData.ReadDsd(dFile);
Now, set up the messages and ranges for the plot progress dialog before plotting the layouts to the PDF file. Delete the temporary DSD file and commit the transaction:
//Get number of layouts in Dsd file int iLayouts = dColl.Count; //Specify plot progress dialog values using (PlotProgressDialog oProg = new PlotProgressDialog(false, iLayouts, true)) { oProg.set_PlotMsgString(PlotMessageIndex.DialogTitle, "Publish Job Progress"); oProg.set_PlotMsgString(PlotMessageIndex. CancelJobButtonMessage, "Cancel Job"); oProg.set_PlotMsgString(PlotMessageIndex. CancelSheetButtonMessage, "Cancel Sheet"); oProg.set_PlotMsgString(PlotMessageIndex. SheetSetProgressCaption, "Job Progress"); oProg.set_PlotMsgString(PlotMessageIndex. SheetProgressCaption, "Sheet Progress"); //Set ranges for sheet and job progress oProg.UpperPlotProgressRange = 100; oProg.LowerPlotProgressRange = 0; oProg.UpperSheetProgressRange = 100; oProg.LowerSheetProgressRange = 0; oProg.IsVisible = true; //Publish selected layouts in a multi-sheet pdf file Autodesk.AutoCAD.Publishing.Publisher publisher = AcadApplication.Publisher; PlotConfig plotConfig = Autodesk.AutoCAD.PlottingServices. PlotConfigManager.SetCurrentConfig ("DWG To PDF.pc3"); publisher.PublishDsd(dFile, oProg); //Delete temporary Dsd file File.Delete(dFile); } trans.Commit(); } catch (Autodesk.AutoCAD.Runtime.Exception ex) { ed.WriteMessage((System.Environment.NewLine + "Problem publishing DSD file. " + ex.Message)); }
Finally, restore the
BackgroundPlot
system variable to its previous value. That's it!finally { //Restore previous BackgroundPlot setting. AcadApplication.SetSystemVariable("BACKGROUNDPLOT", bkgdPlt); }
The dialog box interface that we created for this project is based on the one that we made for the plugin in the Controlling the drawing environment (Should know) recipe. This project requires some additional actions, however. First, the listbox must allow for multiple selections this time, since we may publish more than one layout from a drawing:
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.EditorInput; using AcadApp = Autodesk.AutoCAD.ApplicationServices; namespace Ch7AcadPlugin { /// <summary> /// Interaction logic for Window1.xaml /// </summary> public partial class Window1 : Window { public static string txtDwgToRead; public static string txtSavPdf; public static System.Collections.IList lstLayoutNames; //Create the dialog public Window1() { InitializeComponent(); //Allow multiple selections in listbox this.listBoxLayoutsToPublish.SelectionMode = System.Windows.Controls.SelectionMode.Multiple; }
The
SaveFileDialog
andOpenFileDialog
controls will validate your selection, adding a file extension if necessary. We'll add some code to validate filename entries that were typed into the textboxes.//'Click' event handler for 'OK' button private void okButton_Click(object sender, RoutedEventArgs e) { //Get values from textboxes and listbox if ((txtBoxDwgLocation.Text.Length > 0) && (string.Equals(txtBoxDwgLocation.Text.Substring( txtBoxDwgLocation.Text.Length - 4), ".dwg", StringComparison.OrdinalIgnoreCase) == false)) //Add valid extension if needed txtBoxDwgLocation.Text += ".dwg"; txtDwgToRead = txtBoxDwgLocation.Text; //Get collection of selected layout names lstLayoutNames = listBoxLayoutsToPublish.SelectedItems; if ((txtBoxPdfLocation.Text.Length > 0) && (string.Equals(txtBoxPdfLocation.Text.Substring( txtBoxPdfLocation.Text.Length - 4), ".pdf", StringComparison.OrdinalIgnoreCase) == false)) //Add valid extension if needed txtBoxPdfLocation.Text += ".pdf"; txtSavPdf = txtBoxPdfLocation.Text; this.DialogResult = true; this.Close(); //close the dialog //Call function to publish selected layouts MyCommands.PubLo2Pdf(); } //'Click' event handler for 'Cancel' button private void cancelButton_Click(object sender, RoutedEventArgs e){ this.Close(); //close the dialog }
The
OpenFileDialog
andSaveFileDialog
controls can be set to add a default extension when the user types in a filename, rather than selecting a file://'Click' event handler for dwg-to-read 'Browse...' button private void btnDwgLocation_Click(object sender, RoutedEventArgs e){ //Create OpenFileDialog Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog(); //Set filter for file extension and default file extension dlg.DefaultExt = ".dwg"; dlg.Filter = "Drawing (.dwg)|*.dwg"; dlg.AddExtension = true; //Display OpenFileDialog Nullable<bool> result = dlg.ShowDialog(); //If file is selected, display file name in the textbox if (result == true) { //Get the document filename from the dialog string fn = dlg.FileName; //Populate textbox with the document filename txtBoxDwgLocation.Text = fn; txtDwgToRead = fn; } } //'Click' event handler for pdf-file 'Browse...' button private void btnPdfLocation_Click(object sender, RoutedEventArgs e){ //Create SaveFileDialog Microsoft.Win32.SaveFileDialog dlg = new Microsoft.Win32.SaveFileDialog(); //Set filter for file extension and default file extension dlg.DefaultExt = ".pdf"; dlg.Filter = "PDF (.pdf)|*.pdf"; dlg.AddExtension = true; //Display SaveFileDialog Nullable<bool> result = dlg.ShowDialog(); //If file is selected, display file name in the textbox if (result == true) { //Get the document filename from the dialog string fn = dlg.FileName; //Populate textbox with the document filename txtBoxPdfLocation.Text = fn; } }
In the
Click
event handler for the Read button, we create the auxiliary or side database, into which we read the selected drawing. The purpose is to populate the listbox with the drawing's layout names://'Click' event handler for 'Read' button private void btnReadDwg_Click(object sender, RoutedEventArgs e) { Document doc = AcadApp.Application.DocumentManager.MdiActiveDocument; Editor ed = doc.Editor; this.listBoxLayoutsToPublish.Items.Clear(); if ((txtBoxDwgLocation.Text.Length > 0) && (string.Equals(txtBoxDwgLocation.Text.Substring( txtBoxDwgLocation.Text.Length - 4), ".dwg", StringComparison.OrdinalIgnoreCase) == false)) //Add valid extension if needed txtBoxDwgLocation.Text += ".dwg"; txtDwgToRead = txtBoxDwgLocation.Text; // Create a side database and try to read the dwg file Database db = new Database(false, true); using (db) { try { db.ReadDwgFile(txtDwgToRead, FileShare.Read, false, ""); } catch (System.Exception) { ed.WriteMessage("\nUnable to read drawing file."); return; } Transaction tr = db.TransactionManager.StartTransaction(); using (tr) { //Get each entry in the layout dictionary DBDictionary loDic = (DBDictionary)tr.GetObject(db.LayoutDictionaryId, OpenMode.ForRead, false); foreach (DBDictionaryEntry entry in loDic) { ObjectId loId = entry.Value; //Open the layout for read Layout lo = (Layout)tr.GetObject(loId, OpenMode.ForRead); //Add the layout name to the listbox this.listBoxLayoutsToPublish.Items.Add(lo.LayoutName); } tr.Commit(); }//end transaction } }
We'll also need to add
SelectionChanged
event handlers for the Select layouts listbox and the textboxes where the external drawing and PDF file are specified://'SelectionChanged' event handler for 'Select layouts:' listbox private void listBoxLayoutsToPublish_SelectionChanged(object sender, SelectionChangedEventArgs e) { } //'TextChanged' event handler for 'Specify dwg to read:' textbox private void txtBoxDwgLocation_TextChanged(object sender, TextChangedEventArgs e) { } //'TextChanged' event handler for 'Specify pdf file:' textbox private void txtBoxPdfLocation_TextChanged(object sender, TextChangedEventArgs e) { }
We start here with a UI, similar to the one we made in the Controlling the drawing environment (Should know) recipe. Only this time, we add an OpenFileDialog
control to select an external drawing (.dwg
) file. We also add a button to read the selected drawing into a temporary database. We iterate through the layout dictionary, grabbing the layout names and adding them to a listbox. Unlike in the Controlling the drawing environment (Should know) recipe, this listbox allows us to select multiple layouts.
Once the layout names are selected, we dismiss the temporary database. Now we create a temporary DSD file, which contains the layout information for each sheet that we'll publish in our PDF file. We also add some captions and range limits for the Publish Job Progress dialog box. AutoCAD reads the temporary DSD file and publishes the multi-sheet PDF. When the job is done, we delete the temporary DSD file.