Harry's Code Class: Tips for Programmers (August 2007)

28 Aug, 2007

Get Your Data in a Row (and Column)

Write a Visual Basic routine to automatically create a table
in AutoCAD.

Note from Harry: Harry needs your input. Do you prefer that Harry cover Visual Basic (VBA) or Visual LISP programming in this newsletter? Your answer will help determine how much attention is paid to each topic in future editions. As of this writing, a thread is lingering in Cadalyst's "Hot Tip Harry -- Requests" discussion forum asking for your vote. Currently, it's a close contest. Log on to the forum and cast your vote on the thread, Visual LISP or VBA -- Your Input Counts!

In this month's edition of Harry's Code Class, we'll explore the table object in AutoCAD. A table object is created when you define a table entity in a drawing. Tables are a relatively new addition to the AutoCAD family of entity objects. They allow you to define a neatly structured table of information. You can set up tables to interface with external databases and spreadsheets as well. The goal of our exploration will be to build a table automatically, based on information in the drawing. Specifically, we will document the name and location of each block inserted into model space. Such a table might represent a simple bill of materials or the center holes to be cut in a mounting plate. (I've always thought useful examples are the best to learn from!)

When addressing an application problem as described, you should take several things into account. First is how to access and manipulate the table object. Second is how to control the integrity of the tabulated data as the drawing changes. The implementation language for this application can be any one of the standard tools used for programming AutoCAD, including Visual LISP, VBA, and Object ARX. We will use VBA this time because that language produces a solution in the fewest lines of code. Fewer lines of code means fewer hours spent programming and debugging, and more time to deal with more important matters that befall CAD managers and programmers in today's busy workplace.

How do I know that VBA presents a solution in fewer lines of code? My first clue is the use of objects. Although Visual LISP does allow for object-level manipulation, I know from experience that it requires more actual typing of code. In manipulating objects, the programming features of VBA really pay off. For example, as you type in a reference to an object followed by a period, all the known object methods and properties automatically display in a list for selection. I also know that the debugging features of VBA are extremely handy when exploring elements inside the object hierarchy. I often put a break point in a program, then display the immediate window to type in additional statements displaying the contents of objects -- where once again the automatic listing of object methods and properties options really helps. But enough about the choice of tools -- ultimately, you'll always want to go with the option that's most comfortable for you.

The Table Object
A table object is something that contains data arranged in rows and columns. Graphically, a table contains text and lines according to the table styles. Programmers can access the contents and styles of a table by using a table object. Let's start by creating a new table in AutoCAD using VBA. From that, we get the table object reference.

The first step is to define a symbol that will be used in the remainder of the program to reference the table we will be creating. Because the example I supply will have other utility routines that access the table object, it will be defined as a public symbol.

    Public oTable as AcadTable

Now we start a public macro that will create a table object. Any public subroutine defined in a regular module is considered a public macro. Public macros can be started using AutoCAD's Vbaman or Vbarun commands after the VBA project has been loaded. The following line of code starts the public macro definition.

    Public Sub BuildInsertsTable()

Creating a table object is relatively easy in VBA. The AddTable method is employed with some basic parameters to get the ball rolling. The basic parameters include the insert point, the number of rows, the number of columns, the height of each row, and the width of each column. So let's get our basic data in order. The insertion point will be at zero, and we will use the current drawing text height to compute row and column sizes.

    Dim PT(0 To 2) As Double
    PT(0) = 0#
    PT(1) = 0#
    PT(2) = 0#
    Dim TH As Double
    TH = ThisDrawing.GetVariable("TEXTSIZE")

Now we are ready to add the table entity object to the drawing. The method AddTable by itself is meaningless. It must be used in conjunction with a container object such as a block reference, layout space, or model space. In this example we will be adding the table to the drawing in model space. Our input parameters are for two rows with four columns. The row height will be 1.5 times larger than the current system text height. The column width will be 20 times the current system text height. The result from the AddTable method will be returned and saved in the oTable reference established above as a public name for a table object.

    Set oTable = ThisDrawing.ModelSpace.AddTable(PT, 2, 4, TH * 1.5, TH * 20)

With our new table object defined, we can now insert data. The SetCellValue method allows you to place a value directly into any cell of the table by referencing the row and column number. One thing to note is that the row and column numbers are zero-based, meaning that they begin with zero and increment from there. The following code fills in the header section of the block insertions table.

    oTable.SetCellValue 0, 0, "Block Insertions"
    oTable.SetCellValue 1, 0, "Handle"
    oTable.SetCellValue 1, 1, "Block"
    oTable.SetCellValue 1, 2, "X"
    oTable.SetCellValue 1, 3, "Y"

Grab Your Selection Set
The next step is to get all the block insert objects and add a row for each one in the drawing. To accomplish that, we need a selection set. A selection set is a collection of AutoCAD entity objects created by operator selection or by a parameter-driven search of the drawing. In AutoCAD, a selection set is part of the selection sets collection, so our first step will be to define a named selection set. You can use any name you want, but it's generally a good idea to use something unique so you don't clobber other named sets that might be in use by other applications. This example uses the name BLOCKREFS.

The technique is to first dimension a reference variable, then turn on the error handler so that in case of an error, the routine will advance to execute the next line of the code rather than terminating. The Add method is used to add a new named selection set to the selection sets record. If the error handler returns a zero result, we'll know everything went OK; otherwise, the Item method can be used to access the existing named selection set. The last operation in this sequence is to clear any contents that might already be in the BLOCKREFS selection set.

    Dim SS As AcadSelectionSet
    On Error Resume Next
    Set SS = ThisDrawing.SelectionSets.Add("BLOCKREFS")
    If Err.Number <> 0 Then Set SS = ThisDrawing.SelectionSets.Item("BLOCKREFS")

We want INSERT entity objects in our selection set, and to obtain such, we'll employ a filter. Filters are built by creating two arrays. The first is an integer array that will contain the DXF group codes for the filter. The second array is a variant type and will contain the data associated with the DXF code. To build a filter looking for block inserts, you only need one element in each of the arrays. Combining code 0 (zero) with the data string INSERT is enough of a filter to locate all block inserts in model space. The following lines of code establish the two arrays of data for our filter.

    Dim gpCode(0) As Integer
    Dim dataVal(0) As Variant
    gpCode(0) = 0
    dataVal(0) = "INSERT"

To pass the data into the selection set building method, we must now assign the arrays to variants. A variant data type allows a single reference to the array data.

    Dim groupCode As Variant
    Dim dataCode As Variant
    groupCode = gpCode
    dataCode = dataVal

We can now use the Select method to build the selection set. The parameters to the select method are the type of selection to be performed: a coded value (and in this case we want to look at everything in the drawing), two corner point references, and the filter references.

    SS.Select acSelectionSetAll, , , groupCode, dataCode

The selection set SS is now populated with all the insert objects of the drawing. The next step will be to loop through the selection set and create a new data row in the table for each insert object. A selection set is a collection of entity objects and can be accessed like other collections using Count and Item. Because each member of the set is a block reference, we can use a For-Next loop to step through the collection, get the block insert reference, and call a function to add a new row to the table.

        If SS.Count > 0 Then
          Dim II As Long
          Dim blockRef As AcadBlockReference
          For II = SS.Count - 1 To 0 Step -1
            Set blockRef = SS.Item(II)
            addRow blockRef
          Next II
        End If
    End Sub

Define the Subroutine
That ends the macro, and now we can define the utility subroutine, addRow. The purpose of the utility is to add a row to the table object referenced as oTable. Each row will contain four columns, including the block insert handle, the block name, the x- ordinate, and the y- ordinate. This function receives the block reference (INSERT) object from which the row data is to be extracted.

    Public Sub addRow(insObj As AcadBlockReference)

The first step is to insert a new row. The InsertRows method of the table object needs to know the row number being inserted, the height of the row, and the number of rows to insert. We want to get the height of the previous row and use it to add one row.

    Dim RH As Double RH = oTable.GetRowHeight(oTable.Rows - 1)
    oTable.InsertRows oTable.Rows, RH, 1

The SetCellValue method can now be used to place the data from the block reference into the table. Remember, the row and column numbers start at zero. Column zero is the first column in the table.

    oTable.SetCellValue oTable.Rows - 1, 0, insObj.Handle
        oTable.SetCellValue oTable.Rows - 1, 1, insObj.Name
        Dim PT As Variant
        PT = insObj.InsertionPoint
        oTable.SetCellValue oTable.Rows - 1, 2, PT(0)
        oTable.SetCellValue oTable.Rows - 1, 3, PT(1)
    End Sub

I've run out of room for this month's installment, but the steps outlined here provide the basic approach to creating a table in AutoCAD. I hope you found this example interesting and can take it further for your own applications. Download the code file for more examples to explore on your own. The DVB file contains two macros: the table creation macro outlined here, and another macro for updating the table. In addition, the thisDrawing module contains reactor definitions to automatically update and add new rows as blocks are inserted. To load the DVB file, follow the instructions included in the ZIP file.

Until next time, keep on programmin'.

Download Cadalyst Magazine Special Edition