Create a Multipage DWF File Using VB/VBA14 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 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.
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.
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!