cadalyst
AutoCAD

Create a Multipage DWF File Using VB/VBA

14 Jan, 2005 By: Mike Tuersley Cadalyst

Including more than one page is a bit complicated -- or is it?


A question that has crossed my desk a lot lately is how to create a multipage DWF file through VB/VBA programming. A single page DWF is simple to produce using the DWF plotter drivers inside AutoCAD or the free DWF printer driver, but the multipage DWF is a bit more complicated -- or is it?

Example Project Scope
To demonstrate how to create the multipage DWF file, I will present a form to the user with a list of all the layouts in the current drawing (figure 1). The user can select which of the layouts to include in the final DWF file. I will need a Userform with a ListBox and a CommandButton. The final version of the code that can be downloaded will include more material, but this is enough to demonstrate the functionality. I will leave all the default names for the controls, but I will set the ListBox to use checkboxes and enable multiple selections:

figure
Figure 1. Multipage DWF dialog box.

Option Explicit

Private Sub UserForm_Initialize()
  '+--- Startup
  Dim oLO as AcadLayout
  For Each oLO In ThisDrawing.Layouts
    ListBox1.AddItem oLO.Name
  Next
  'setup controls
  CommandButton1.Caption = "Print"
  ListBox1.ListStyle = fmListStyleOption
  ListBox1.MultiSelect = fmMultiSelectMulti
  Me.Caption = "xPage DWF Print Example"
End Sub

Private Sub CommandButton1_Click()
  '+--- user is printing
  Dim cLO As New Collection
  Dim iIndex As Integer
  For iIndex = 0 To Listbox1.ListCount - 1
    If ListBox1.Selected(iIndex) = True Then
      cLO.Add ListBox1.List(iIndex)
    End If
  Next
  'hide form so code can run
  Me.Hide
  'call to printing
  MultiDWFPrint cLO
  'show form again
  Me.Show
End Sub

Note: The next-to-last line is the action call to print, which will be created shortly.

DWF Background Information
The DWF file format is just a compressed archive file similar to a ZIP file. If you have a DWF file handy, make a copy and rename its extension to .zip. When you try opening it using your ZIP software, you should see a series of folders and files within the ZIP, which consist of XML data structures, PNG graphics and Meta data information.

A file of particular interest to us is the DSD (Drawing Set Description) file located in the ePlotGlobal folder. The simplest explanation of a DSD file that it's a table of contents that tells AutoCAD's DWF engine what is required for the multipage DWF file. AutoCAD uses this file to process the Layout tabs needed. Here is an example of a DSD file:

[DWF6Version]
Ver = 1
[DWF6Sheet:1st Layout Name]
DWG=C: \Wilhome.dwg
Layout = 1st Layout Name
<?sheets between 1st and nth?>
[DWF6Sheet:Nth Layout Name]
DWG=C:\ Wilhome.dwg
Layout = Nth layout Name
[Target]
Type=1
DWF=C:\Wilhome.dwf
PWD=

Notice the DSD file is nothing more than a Windows INI file -- it is separated into sections and values. The first two lines are set in stone -- they give the version of the DWF file. The very last line is optional to allow password protection if desired. All the lines in between are descriptions of the layouts that comprise the multipage DWF file. Each layout requires a section stating the name of the layout. The following two values are the drawing file and its layout to process as part of the DWF file.

Also be aware that there may be more value keys within your DSD file. The structure presented above represents the minimum structure required in a DSD file. This is important because our next step is to create our own DSD file.

DSD File Creation
The DSD file is required to generate a multipage DWF file. Whether the DSD file is an existing one or a new one created as part of this project has to be determined by you. This wouldn't be a complete article if I didn't create my own on-the-fly, though!

I'll use very basic code for the file creation and add a loop that will write each layout name the user selected in the ListBox of our form. This is the code I will use:

Private Function CreateDSDFile(cLOs As Collection, _
                 sDWFFile As String) As Boolean
  Dim iCntr As Integer  'generic counter
  Dim sTemp As String   'generic temporary string
  Dim LogNum As Integer 'reference to file object
  On Error GoTo Exit_Early
  'check to see if DSD file exists and delete it if it does
  If FileLen(sDWFFile) > 0 Then Kill sDWFFile
  'create the new dsd file
  LogNum = FreeFile
  'write to the file
  Open sDWFFile For Append Access Write Lock Write As #LogNum
    Print #LogNum, "[DWF6Version]"
    Print #LogNum, "Ver = 1"
    'iterate thru the layouts collection
    For iCntr = 1 To cLOs.Count
      'grab the next layout name in the collection
      sTemp = cLOs.Item(iCntr)
      Print #LogNum, "[DWF6Sheet:" & sTemp & "]"
      Print #LogNum, "DWG=" & m_dwgName
      Print #LogNum, "Layout = " & sTemp
    Next
    Print #LogNum, "[Target]"
    Print #LogNum, "Type=1"
    Print #LogNum, "DWF=" & sDWFFile
    Print #LogNum, "PWD="
  Close #LogNum
  'send back success
  CreateDSDFile = True
  Exit Function
  
Exit_Early:
  Err.Clear
  'send back failure
  CreateDSDFile = False
End Function

Here I have chosen to use a function that expects a collection and a string to be passed to it. The collection will contain the list of layout names the user selected in the dialog box, and the string will be the name of the DSD file. For this example, the DSD file will be located in the same directory as the current drawing and have the same name as the drawing with the DSD extension. The function itself passes back a Boolean value that will indicate whether it was successful in creating the DSD file.

DWF Printing
So far, so good! This isn't really that difficult, is it? Well, now comes the tricky part.

Unfortunately, the only way to generate the DWF file from AutoCAD is to use the Publish command, which presents a series of problems because Publish is not exposed through the AutoCAD API. The first problem is that Publish uses a dialog box that we cannot allow. If the Publish command does fire its dialog box, my program will stop because I have no way of interfacing with it. To get past this, I will use the command line version of Publish: _PUBLISH. To do this, I must suppress the display of file dialog boxes by turning off the system variable FILEDIA:

ThisDrawing.SetVariable "FILEDIA", 0

The next problem is actually calling the _PUBLISH command. To do this, I have to use the SendCommand method. The SendCommand method should always be reserved as the very last option because it introduces a whole can of worms to your programming efforts. Because the SendCommand is basically a method of forcing typed commands into AutoCAD's Command line, it is very easy for my program to lose focus because it has no way to determine when the SendCommand code has finished processing. VB/VBA code just passes the string to the SendCommand and continues on its merry way, which usually results in an error because it is running faster than the command it passed. So, if you have to use the SendCommand, always make it the very last command in the program.

The next problem I could have is due to the name of the file I am going to generate. If the DWF file has spaces in its name, SendCommand will see those spaces as Enters and basically split the file name apart, which will bomb my program. To eclipse this hurdle, I must pass the name of the DWF file surrounded by quotation marks without the VB/VBA command interpreter mistaking the extra quotation marks as VB string indicators. I will opt to pass a LISP command to SendCommand so I can concatenate character codes [CHR(#)] to my real string variables and fake out the interpreter.

Sub MultiDWFPrint (cLayOuts As Collection)
  Dim dwgName As String
  Dim dwfName As String
  Dim dsdName As String
  Dim sTxt As String
  'grab the drawing variables we need
  dwgName = ThisDrawing.FullName
  'check to make sure we have layouts
  If cLayOuts.Count > 0 Then
    'build the DSD file name
    dsdFile = Replace(dwgName, ".dwg", ".dsd")
    'build the DWF file name
    dwfFile = Replace(dwgName, ".dwg", ".dwf")
    'create the DSD file
    If CreateDSDFile(cLayOuts, dsdFile) = True Then
      'publish our multipage DWF file
      With ThisDrawing
        sTxt = Replace(dsdFile, "\", "/")
        'turn FILEDIA *OFF* to avoid dialog boxes
        .SetVariable "FILEDIA", 0
        .Regen acActiveViewport
        'Publish layouts
        .SendCommand "(command " & Chr(34) & _
                     "-PUBLISH" & Chr(34) & _
                     " " & Chr(34) & sTxt & _
                     Chr(34) & ")" & vbCr
      End With
    Else
      MsgBox "Error creating DSD file: " & dsdFile
      GoTo Leave_Now
    End If
  Else
    MsgBox "There are no valid layouts within dwg file"
  End If
  
Leave_Now:
  'clean up before leaving
  If Not cLayOuts Is Nothing Then Set cLayOuts = Nothing
'

End Sub

Note the line Replace(dsdFile, "\", "/") is required because LISP will interpret the forward slash as a noncharacter so it must be replaced with a backslash. This is a LISP requirement.

Wrapping Up
I have one final issue to resolve. If the program is successful, my AutoCAD session now has its file dialog support disabled! Remember how I said that if you use SendCommand, it must be the very last command? In the example so far, it is the next-to-last command, and you can see how the SendCommand is still running behind the VBA code because the Userform reappears before the _PUBLISH command has finished running.

I could forget that I thought of this problem and just wait for my end users to complain, or I could add code to the form close event that sets FILEDIA back to 1:

Private Sub UserForm_Terminate()
  ThisDrawing.SetVariable "FILEDIA", 1
End Sub

Kick It Up a Notch!
Okay, my Wrapping Up section is a little less substantial than usual because what if I am writing an application that does not contain a form? How will I turn FILEDIA back on if that is the case? Well, we have plenty of unique ways to handle that scenario, and I will leave you to explore them. For example, one workaround might be to add a loop that reads a USER variable:

Dim sTest As String
Do Until sTest = "DONE"
  sTest = ThisDrawing.GetVariable("USERS2")
Loop

In this scenario, the USERS2 variable value could be set through code added to an event such as EndCommand or EndPlot. See what you can come up with to resolve this issue. If you think you have an ingenious solution, e-mail it to me. I just might publish it. Take care until next month!


About the Author: Mike Tuersley


More News and Resources from Cadalyst Partners

For Mold Designers! Cadalyst has an area of our site focused on technologies and resources specific to the mold design professional. Sponsored by Siemens NX.  Visit the Equipped Mold Designer here!


For Architects! Cadalyst has an area of our site focused on technologies and resources specific to the building design professional. Sponsored by HP.  Visit the Equipped Architect here!