Book Image

Instant Autodesk AutoCAD 2014 Customization with .NET

By : Tom Nelson
Book Image

Instant Autodesk AutoCAD 2014 Customization with .NET

By: Tom Nelson

Overview of this book

<p>AutoCAD's .NET API can be used to create small but powerful applications that help CAD users achieve productivity gains and improve quality. CAD users can accelerate drafting and design processes, improve drawing accuracy, minimize time spent on repetitive or demanding tasks, and reduce errors. In short, users can deliver better drawings faster with customized CAD tools.</p> <p>Learn how to use AutoCAD's .NET API to create your own high-powered, custom applications for AutoCAD. This book is a toolbox of small projects for handling common AutoCAD tasks. You can add to these recipes to develop your own specialized AutoCAD program library. Clear, step-by-step instructions and complete code examples illustrate the process, making it easy to develop your own custom AutoCAD tools.</p> <p><br />Giving you the building blocks of AutoCAD application development, you&rsquo;ll learn to create compact user interfaces for your AutoCAD plugins and add command buttons to the ribbon interface. Next, you&rsquo;ll create programs to insert and modify AutoCAD block and attribute references, as well as working with custom data stored on AutoCAD objects. Learn how to publish layouts from external drawings in multi-sheet PDF files, export AutoCAD data to MS Excel for processing, and respond to AutoCAD event notifications (such as when an object is selected). With the tools presented in this book, you can develop and implement new functionality to address your specialized business needs.</p>
Table of Contents (7 chapters)

Publishing a drawing set (Should know)


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.

Getting ready

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).

How to do it...

  1. 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);
            }
  2. Once the user clicks on OK, we are almost ready to publish. First, we must set the AutoCAD system variable BackgroundPlot to 0, 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);
  3. 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);
          }
  4. 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);
  5. 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);
  6. 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));
    }
  7. Finally, restore the BackgroundPlot system variable to its previous value. That's it!

    finally {
       //Restore previous BackgroundPlot setting.
       	AcadApplication.SetSystemVariable("BACKGROUNDPLOT", bkgdPlt);
    }
  8. 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 and OpenFileDialog 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
    }
  9. The OpenFileDialog and SaveFileDialog 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;
       }
    }
  10. 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
       }                    
    }
  11. 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) {
    }

How it works...

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.