A Replacement for VB Evaluate(string)

To graph a function we need to evaluate a string into its math equivalent. Excel VBA has the Application.Evaluate method which takes a variant for an argument and will evaluate a wide range of math symbols, with or without quotations – whatever is between the parentheses. In Visual Studio that doesn’t exist. We want to type a function (the right side of Y = some expression of X) into a textbox on a form, save it to a string variable, then calculate the value to a double for a range of X values. For instance,

strfunc = “X^3 – 3*X^2 + 2*x + 2”

A loop calculates X and Y values starting at X minimum, incrementing by a small X increment, ending at X maximum, and saving all the values to an array to be an argument for AddLightWeightPolyline.

the number of points to be stitched into a graph is calculated –
numpts = CInt((Xmax – Xmin) / Xinc) ‘number of line segments

there is always one more point than line segments
numpts = numpts + 1

the array of doubles is sized –
ReDim Pt(0 To numpts * 2 – 1) ‘store x and y for one pt

after the number of points has been calculated, and the array is ready, the loop is

For i = 1 To numpts
x = Xmin + ((i - 1) * Xinc)
y = eval_func(strfunc, x)
pt(i * 2 - 2) = x
pt(i * 2 - 1) = y
Next i

X is recalulated each time through the loop. Rather than adding the increment to a running sum, its more accurate to use the loop counter to multiply the increment, because of the imprecision of doubles. With odd numbers you might eventually run one cycle short or long. The loop counter should always be an integer. Calculation of Y is handed off to a function with the function string and the current value of X. The function substitutes the numerical value of X whereever X the symbol appears.

Debug.Print strfunc
strfunc = Replace(strfunc, “x”, x, , , vbTextCompare)
Debug.Print strfunc

the first time thru where X is at the starting value of, say, -6, the string and the modified string look like –

X^3 – 3*X^2 + 2*x + 2
-6^3 – 3*-6^2 + 2*-6 + 2

then the final scalar value is calculated and the value returned as Y.

dbl_result = Evaluate(strfunc)

This code was being run from Excel. Evaluate was a method of the top level Application.

For Visual Studio VB.Net we need a replacement for Evaluate.

A lot of people have asked this question. There are quite a few homebuilt parser class solutions. Another method is to link to another language and use its Eval method. A quick easy solution was found, credit to user kleinma

https://www.vbforums.com/showthread.php?412232-RESOLVED-02-03-Question-about-using-VBScript-(Eval)-in-VB-NET

As kleinma says, “Set a reference in your app to “Microsoft Script Control 1.0″ It is under the COM tab in references, not in the .NET tab”

A lot of testing will show if its a decent fix or just the first try.

    Public Xmin As Double
    Public Xmax As Double
    Public Xinc As Double
    Public Ylim As Double
    Public g_stop As Boolean
    Public g_err_counter As Integer


    Function calc_xy(strfunc As String) As Double()
        'returns an array of x and y doubles
        'Xmax, Xmin, Xinc, Ylim are all assumed to have values
        Dim x, y As Double
        Dim i, numpts As Integer
        Dim Pt() As Double

        g_err_counter = 0
        g_stop = False

        numpts = CInt((Xmax - Xmin) / Xinc) 'number of line segments 
        numpts = numpts + 1   'one more pt than line segment
        ReDim Pt(0 To numpts * 2 - 1) 'store x and y for one pt

        For i = 1 To numpts
            x = Xmin + ((i - 1) * Xinc)
            y = eval_func(strfunc, x)

            If g_stop Then  'if eval_func set g_stop then
                MsgBox("errorcounter at 3 - exiting")
                Exit For
            End If
            Pt(i * 2 - 2) = x
            Pt(i * 2 - 1) = y
        Next i

        Return Pt
    End Function

    Function eval_func(ByVal strfunc As String, x As Double) As Double
        'returns a double
        'on error returns Ylim but sets a flag to stop after 3 errors
        'which would indicate a bad formula
        'one or two error is ok because havent solved the mid-graph asymptote yet
        Dim result As Double
        strfunc = Replace(strfunc, "x", x.ToString, , , vbTextCompare)

        Dim SC As New MSScriptControl.ScriptControl
        SC.Language = "VBSCRIPT"
        Try
            result = Convert.ToDouble(SC.Eval(strfunc))
        Catch ex As Exception         
            MessageBox.Show(ex.GetType.ToString & " " & ex.Message)
            g_err_counter = g_err_counter + 1
            If g_err_counter > 2 Then
                g_stop = True
            End If
            result = Ylim
        End Try

        Return result
    End Function

Transformation Matrices – 2D Demo

The theory of matrices was first developed (or so I read) in relation to geometric transformations.

The best single reference that explained Transformation Matrices to me esp with regards to graphics programming was Rod Stephens book Visual Basic Graphics Programming, which is 20+ years old. It’s a comprehensive but clear introduction. There are also many linear algebra math videos on Khan, Youtube etc that i am just starting to explore. Stephens multiplies his vector from the left of the matrix. Everyone else does it from the right, so i cannot use his examples as-is, but looking at his methods has been very helpful.

A vector is also a matrix, and its a point. Multiplying a square matrix by a column vector yields another column vector. Thats how the point gets transformed. The transformation matrix can contain numbers for scaling, translating (moving) and rotation.

The basic idea can be shown with a 2D autocad implementation and then extend to full 3D. Matrix math, and multiplication of matrices is somewhat hard to grasp, but programming it from scratch is good reinforcement. The basic 3D matrix is a 4×4 grid of doubles. The 2D matrix is 3×3.

The first sub initializes an identity matrix, which is the base. The subs that create Translation, Scaling and Rotation numbers will modify the identity matrix. To combine these operations into one, multiplication of two 3×3 matrices is done. Finally to apply the matrix to a point, the matrix is multiplied by the 1×3 point vector.

In reality, we are going to use arrays as matrices, and the array of a 1×3 is actually just a 3, but we are going to treat it as a column. VB.net arrays start with index zero. Most matrix references or maybe all use 1 as the base number.

Here is the 3×3 initial matrix with value 1 on the main diagonal and zero elsewhere. Its a function that returns the result and does not take any arguments.

show_mat(M) is a sub to show the matrix in the immediate window screen for checking.

    Public Function m3x3_ID() As Double(,)
        Dim M(0 To 2, 0 To 2) As Double
        Dim i, j As Integer

        For i = 0 To 2
            For j = 0 To 2
                If i = j Then
                    M(i, j) = 1
                Else
                    M(i, j) = 0
                End If
            Next
        Next

        show_mat(M)
        Return M
    End Function

    Public Sub show_mat(M(,) As Double)
        Dim i, j As Integer
        Dim str As String = ""
        For i = 0 To UBound(M, 1)
            For j = 0 To UBound(M, 2)
                If j = 0 Then
                    str = str & vbCrLf
                End If
                str = str & M(i, j) & vbTab
            Next
        Next

        Debug.Print(str)
    End Sub

We need a sub to multiply a 3×3 matrix by a 1×3 vector. MxV. This is a simple program that takes the Matrix and Vector as arguments and returns a Vector.
For 2D the Z value is not required but is required by autocad, leading to a bit of complication we wont have with 3D.

    Function MxV(M(,) As Double, V() As Double) As Double()
        Dim V2(2) As Double

        V2(0) = M(0, 0) * V(0) + M(0, 1) * V(1) + M(0, 2) * 1
        V2(1) = M(1, 0) * V(0) + M(1, 1) * V(1) + M(1, 2) * 1
        V2(2) = M(2, 0) * V(0) + M(2, 1) * V(1) + M(2, 2) * 1

        'need to zero this out for 2D demo purpose
        V2(2) = 0

        show_pt(V2)
        Return V2

    End Function

Matrices are combined by multiplication. I still have to show myself why that works, when rotation and scaling use the same cells. The multiplication of two 3×3 matrices is row by column. You could do it with brute force, but studying the concise math definition –

c(ij) = a(i1)b(1j) + a(i2)b(2j) + a(i3)b(3j)

– shows the basic i and j loop variables and the third variable just goes from 1 to 3. Modifying slightly for our 0 to 2 array gives a 3 variable loop. Matrices A and B are 3×3. The order matters. Multiplication is not commutative. A movement then a rotation is different than a rotation then a movement. The function returns the new matrix.


    Function MxM(A(,) As Double, B(,) As Double) As Double(,)
        Dim C(2, 2) As Double
        Dim i, j, n As Integer
        Dim value As Double

        For i = 0 To 2
            For j = 0 To 2
                value = 0
                For n = 0 To 2
                    value = value + A(i, n) * B(n, j)
                Next
                C(i, j) = value
            Next
        Next

        show_mat(C)
        Return C
    End Function

Converting Autocad VBA ActiveX to Visual Studio

The Autocad ActiveX and VBA Developer’s Guide, Work in Three Dimensional Space (chap 8 in the old print version), is available with 10 sample code programs here

also in your autocad installation as a CHM file, (very easy to work with)
ProgramFiles\Common Files\Autodesk Shared\acadauto.chm

these sample programs paste in to Visual Studio with a few but not too many problems. Below is a screenshot of the first one. On the left is the virgin code. On the right is the corrected version which runs as intended.

Visual Studio throws up 8 errors on paste. (see ThisDrawing discussion below)
Let and Set assignment is no longer supported. You simply delete them.
acRed and acBlue is not declared. Hovering over the word acRed, Visual Studio suggests the fix, add the namespace ACAD_COLOR
Variant is no longer a supported type: Use the Object type instead.

Changing Variant to Object does eliminate the error on that line, but creates errors where it is accessed. Arrays are an area where dot.net has fixed logical inconsistencies in VBA. Autocad declared a return value of variant because static arrays could not accept an assigment. Dynamic arrays could accept assignment, but that was not in the very first version of VBA, and apparently never made it into the Autocad ActiveX implementation. There is no longer a distinction in dot.net between dynamic and static arrays.

We know the point data for polylines is a Double, and we know the autocad Coordinates method will return a variable number of points, so lets declare the variable as we would a double type dynamic array.

Dim get2Dpts() as Double

now we get an error on assigment –
get2Dpts = pline2DObj.Coordinates
Option Strict On disallows implicit conversions from ‘Object’ to ‘Double’.

anytime you get that and you know your variables are convertible the solution is Ctype(var, type)
get2Dpts = CType(pline2DObj.Coordinates, Double())

Autocad VBA sample code references a variable called ThisDrawing which is always available in the VBA environment. From Visual Studio, I run a sub called Connect_Acad that makes the connection and uses a global AcadDocument variable which can be named ThisDrawing and that is what i have done here. It is added to the top of the sample code.

Make those changes and it runs as is.

There is a simplification on the array assignments available in Visual Studio. In VBA I made subroutines to try to make the awkward point data assigments flow easier. VB.Net has a good new array feature called an initialization list.

you can change all this –

Dim points2D(0 To 5) As Double
Dim points3D(0 To 8) As Double

‘ Define three 2D polyline points
points2D(0) = 1 : points2D(1) = 1
points2D(2) = 1 : points2D(3) = 2
points2D(4) = 2 : points2D(5) = 2

‘ Define three 3D polyline points
points3D(0) = 1 : points3D(1) = 1 : points3D(2) = 0
points3D(3) = 2 : points3D(4) = 1 : points3D(5) = 0
points3D(6) = 2 : points3D(7) = 2 : points3D(8) = 0

to this –

‘ Define three 2D polyline points
Dim points2D() As Double = {1, 1, 1, 2, 2, 2}

‘ Define three 3D polyline points
Dim points3D() As Double = {1, 1, 0, 2, 1, 0, 2, 2, 0}

side by side screenshots –

*******************
NewUCS
*******************
9 errors on paste
Set
Method arguments must be enclosed in parentheses.
acWorld and acUCS is not declared.

fix those and down to 4 all due to Variant.

Dim WCSPnt As Variant
Dim UCSPnt As Variant

Lets try that again where we change the variant array to its target double array.
Dim WCSPnt() As Double
Dim UCSPnt() As Double

Again we get an error where Autocad Utility Getpoint is returning an Object, originally it was a Variant. We know its a double array so we can use the Ctype function.

The other and last error though is on the boolean argument in Utility.TranslateCoordinates.
UCSPnt = ThisDrawing.Utility.TranslateCoordinates(WCSPnt, AcCoordinateSystem.acWorld, AcCoordinateSystem.acUCS, False)
Strict On disallows implicit conversions from ‘Boolean’ to ‘Integer’.

That makes me want to look at the ActiveX Reference Guide under Objects Utility TranslateCoordinates. Its a flag possible values are True or False but its a Long type not Boolean. it affects whether the first argument is treated as an absolute point value or a vector. i dont quite know why its implemented like that but i can go with the value zero to get the intent.

That allows another error which has been in the queue on the same line. which is fixed by the familiar Ctype function.
And now it runs.

Arrays in VB.Net are all normally zero-based (with some exceptions). So VBA declarations such as Dim pt (0 to 2) as double can be changed to Dim pt(2) as Double. But we can also use the initialization list to clean up.

change this –
Dim origin(0 To 2) As Double
Dim xAxisPnt(0 To 2) As Double
Dim yAxisPnt(0 To 2) As Double

origin(0) = 4 : origin(1) = 5 : origin(2) = 3
xAxisPnt(0) = 5 : xAxisPnt(1) = 5 : xAxisPnt(2) = 3
yAxisPnt(0) = 4 : yAxisPnt(1) = 6 : yAxisPnt(2) = 3

to this –
Dim origin() As Double = {4, 5, 3}
Dim xAxisPnt() As Double = {5, 5, 3}
Dim yAxisPnt() As Double = {4, 6, 3}


    Sub Ch8_NewUCS()
        Call Connect_acad()
        Dim ucsObj As AcadUCS

        Dim origin() As Double = {4, 5, 3}
        Dim xAxisPnt() As Double = {5, 5, 3}
        Dim yAxisPnt() As Double = {4, 6, 3}

        ' Add the UCS to the UserCoordinatesSystems collection
        ucsObj = ThisDrawing.UserCoordinateSystems.Add(origin, xAxisPnt, yAxisPnt, "New_UCS")
        ' Display the UCS icon
        ThisDrawing.ActiveViewport.UCSIconAtOrigin = True
        ThisDrawing.ActiveViewport.UCSIconOn = True

        ' Make the new UCS the active UCS
        ThisDrawing.ActiveUCS = ucsObj
        MsgBox("The current UCS is : " & ThisDrawing.ActiveUCS.Name & vbCrLf & " Pick a point in the drawing.")

        ' Find the WCS and UCS coordinate of a point
        Dim WCSPnt() As Double
        Dim UCSPnt() As Double

        WCSPnt = CType(ThisDrawing.Utility.GetPoint(, "Enter a point: "), Double())
        UCSPnt = CType(ThisDrawing.Utility.TranslateCoordinates(WCSPnt, AcCoordinateSystem.acWorld, AcCoordinateSystem.acUCS, 0), Double())

        MsgBox("The WCS coordinates are: " & WCSPnt(0) & ", " & WCSPnt(1) & ", " & WCSPnt(2) & vbCrLf &
     "The UCS coordinates are: " & UCSPnt(0) & ", " & UCSPnt(1) & ", " & UCSPnt(2))
    End Sub

*******************
TranslateCoordinates
*******************
8 errors on paste
Set, Arguments not enclosed in parentheses, unfound enumerations that need namespaces, and use of Variant arrays.

Dim firstVertex As Variant
firstVertex = plineObj.Coordinate(0)

looking up Polyline Object in the ActiveX Rerference Guide then clicking on Coordinate says the property value is a two or three element array of doubles.

change to –
Dim firstVertex() As Double
firstVertex = CType(plineObj.Coordinate(0), Double())

and
Dim coordinateWCS As Variant
coordinateWCS = ThisDrawing.Utility.TranslateCoordinates(firstVertex, AcCoordinateSystem.acOCS, AcCoordinateSystem.acWorld, False, plineNormal)

change to –
Dim coordinateWCS() As Double
coordinateWCS = CType(ThisDrawing.Utility.TranslateCoordinates(firstVertex, AcCoordinateSystem.acOCS, AcCoordinateSystem.acWorld, 0, plineNormal), Double())

    Sub Ch8_TranslateCoordinates()
        Call Connect_acad()
        Dim plineObj As AcadPolyline
        Dim points() As Double = {1, 1, 0, 1, 2, 0, 2, 2, 0, 3, 2, 0, 4, 4, 0}

        ' Create a light weight Polyline object in model space
        plineObj = ThisDrawing.ModelSpace.AddPolyline(points)

        ' Find the X and Y coordinates of the first vertex of the polyline
        Dim firstVertex() As Double
        firstVertex = CType(plineObj.Coordinate(0), Double())

        ' Find the Z coordinate for the polyline using the elevation property
        firstVertex(2) = plineObj.Elevation

        ' Change the normal for the pline so that the difference between the coordinate systems is obvious.
        Dim plineNormal() As Double = {0#, 1.0#, 2.0#}
        plineObj.Normal = plineNormal

        Dim coordinateWCS() As Double
        coordinateWCS = CType(ThisDrawing.Utility.TranslateCoordinates(firstVertex, AcCoordinateSystem.acOCS, AcCoordinateSystem.acWorld, 0, plineNormal), Double())

        ' Display the coordinates of the point
        MsgBox("The first vertex has the following coordinates:" & vbCrLf & "OCS: " & firstVertex(0) & ", " &
     firstVertex(1) & ", " & firstVertex(2) & vbCrLf & "WCS: " & coordinateWCS(0) & ", " & coordinateWCS(1) & ", " & coordinateWCS(2))
    End Sub

*************
Create3DMesh
************
here is one we can clean up quite a bit. it pastes in with only two errors, both trivial. It runs just by deleting the word Set, adding AcadApp. in front of ZoomAll and adding Connect_acad as the first line .
Here is the original VBA from Autocad.

Sub Ch8_Create3DMesh()
    Dim meshObj As AcadPolygonMesh
    Dim mSize, nSize, Count As Integer
    Dim points(0 To 47) As Double

    ' create the matrix of points
    points(0) = 0: points(1) = 0: points(2) = 0
    points(3) = 2: points(4) = 0: points(5) = 1
    points(6) = 4: points(7) = 0: points(8) = 0
    points(9) = 6: points(10) = 0: points(11) = 1
    points(12) = 0: points(13) = 2: points(14) = 0
    points(15) = 2: points(16) = 2: points(17) = 1
    points(18) = 4: points(19) = 2: points(20) = 0
    points(21) = 6: points(22) = 2: points(23) = 1
    points(24) = 0: points(25) = 4: points(26) = 0
    points(27) = 2: points(28) = 4: points(29) = 1
    points(30) = 4: points(31) = 4: points(32) = 0
    points(33) = 6: points(34) = 4: points(35) = 0
    points(36) = 0: points(37) = 6: points(38) = 0
    points(39) = 2: points(40) = 6: points(41) = 1
    points(42) = 4: points(43) = 6: points(44) = 0
    points(45) = 6: points(46) = 6: points(47) = 0

    mSize = 4: nSize = 4

    ' creates a 3Dmesh in model space
    Set meshObj = ThisDrawing.ModelSpace. _
 Add3DMesh(mSize, nSize, points)

    ' Change the viewing direction of the viewport
    ' to better see the cylinder
    Dim NewDirection(0 To 2) As Double
    NewDirection(0) = -1
    NewDirection(1) = -1
    NewDirection(2) = 1
    ThisDrawing.ActiveViewport.Direction = NewDirection
    ThisDrawing.ActiveViewport = ThisDrawing.ActiveViewport
    ZoomAll
End Sub

code that runs in visual studio

   Sub Ch8_Create3DMesh()
        Call Connect_acad()
        Dim meshObj As AcadPolygonMesh
        Dim mSize, nSize, Count As Integer

        Dim points() As Double =
            {0, 0, 0,
             2, 0, 1,
             4, 0, 0,
             6, 0, 1,
             0, 2, 0,
             2, 2, 1,
             4, 2, 0,
             6, 2, 1,
             0, 4, 0,
             2, 4, 1,
             4, 4, 0,
             6, 4, 0,
             0, 6, 0,
             2, 6, 1,
             4, 6, 0,
             6, 6, 0}

        mSize = 4 : nSize = 4
        ' creates a 3Dmesh in model space
        meshObj = ThisDrawing.ModelSpace.Add3DMesh(mSize, nSize, points)

        ' Change the viewing direction of the viewport 
        Dim NewDirection() As Double = {-1, -1, 1}
        ThisDrawing.ActiveViewport.Direction = NewDirection
        ThisDrawing.ActiveViewport = ThisDrawing.ActiveViewport
        acadApp.ZoomAll()
    End Sub

*************
GetUCSMatrix
************
sample code from object UCS method GetUCSMatrix in ActiveX Reference Guide

Here is the corrected code. I have added a loop to read and print the matrix values.
The orig program creates a new UCS, makes it active, then draws a circle. That illustrates that coordinate input from ActiveX is always interpreted as World coordinates (VBA ignores the current UCS). The program then runs

newMatrix = UCSobj.GetUCSMatrix
circleobj.Transformby(newMatrix)

which moves the circle from its world coordinates to the equivalent UCS coordinates.

We never have to examine the Matrix or know what its dimensions are. I added a loop to read it. The matrix is a square 4×4. It contains point values as columns. The bottom row seems to be unused.

col1 is the same data as the variable UCSXDIR, its the X vector from the UCS origin.
col2 is the same data as the variable UCSYDIR, its the Y vector from the UCS origin
col3 is the Z vector, which is obtainable from the XY vectors by the right hand rule.
col4 is the origin.

if we were writing our own matrix we would do

dim Matrix(3 , 3) as Double

or

dim Matrix( , ) as Double =
{ {1,0,0,2},
{ 0,1,0,2},
{ 0,0,1,0},
{ 0,0,0,1} }

these are direction vectors.
UCSXDIR = 1,0,0
UCSYDIR = 0,1,0
Z DIR = 0,0,1

absolute point
UCSORG = 2,2,0

this is not the whole story, Transformation Matrices are a fascinating topic in linear algebra. TransformBy has some useful examples, including scaling, which occurs on the main diagonal. ARX Developers Guide (search on transformation matrix) has better help page than ActiveX. First 3 columns are both rotation and scaling. last column is translation. Transformation matrixes can be combined so translation, scaling and rotation are all accomplished in a single operation.

TransformBy is a method of the elementary AcadEntity, meaning it is enabled for every object in the drawing.

Sub Example_GetUCSMatrix()
        ' This example creates a new UCS and finds the UCS matrix for it.
        ' It then creates a circle using WCS coordinates and
        ' transforms the circle for the UCS.
        Call Connect_acad()

        ' Define a new UCS and turn on the UCS icon at the origin.
        Dim ucsObj As AcadUCS
        Dim origin() As Double = {2, 2, 0}
        Dim xAxisPoint() As Double = {3, 2, 0}
        Dim yAxisPoint() As Double = {2, 3, 0}

        'Dim xAxisPoint() As Double = {4, 4, 0}
        'Dim yAxisPoint() As Double = {0, 4, 0}

        ucsObj = ThisDrawing.UserCoordinateSystems.Add(origin, xAxisPoint, yAxisPoint, "UCS1")
        ThisDrawing.ActiveUCS = ucsObj

        Dim vportObj As AcadViewport
        vportObj = ThisDrawing.ActiveViewport
        vportObj.UCSIconOn = True
        vportObj.UCSIconAtOrigin = True
        ThisDrawing.ActiveViewport = vportObj

        ' Create a circle using WCS coordinates
        Dim circleObj As AcadCircle
        Dim center() As Double = {1, 1, 0}
        Dim radius As Double = 0.5
        circleObj = ThisDrawing.ModelSpace.AddCircle(center, radius)
        acadApp.ZoomAll()

        ' Get the UCS transformation matrix
        Dim TransMatrix(,) As Double
        TransMatrix = CType(ucsObj.GetUCSMatrix(), Double(,))

        Dim str As String
        Dim r, c As Integer
        For r = 0 To UBound(TransMatrix, 1)
            For c = 0 To UBound(TransMatrix, 2)
                str = "r = " & CStr(r) & " c = " & CStr(c) & "   " & CStr(TransMatrix(r, c))
                Debug.Print(str)
            Next
        Next

        ' Transform the circle to the UCS coordinates
        MsgBox("Transform the circle.", , "GetUCSMatrix Example")
        circleObj.TransformBy(TransMatrix)
        circleObj.Update()
        MsgBox("The circle is transformed.", , "GetUCSMatrix Example")
        acadApp.Update()
    End Sub

Autocad Object Types in Visual Studio

The basic object, then AcadObject, then AcadEntity, then the specialized specific top objects. This is the object inheritance hierarchy in autocad. Most objects contained by the document derive from the most general AcadObject. Hence most objects have those basic properties, which include Handle, ObjectName, ObjectID and OwnerID. All the graphic elements, things you can see, like lines and circles, further derive from the AcadEntity object. The quickest way to survey this in the ActiveX Reference Guide clicking on the graphical Object Model. The dwg document object itself does not inherit from AcadObject, but most of what it contains does.

TreeView and ListView controls in Visual Studio are a little changed in Visual Studio from VB6. TreeView no longer uses a key or a relationship parameter in the argument list. There is a tag property that takes the place of key. Building a hierarchy is done by saving the node to a variable, then using its Nodes.Add method to create a new node directly as a child node.

To connect TreeView click event to the ListView display, MS uses the tag property, illustrated in this link.

The handle property of all AcadObjects is a string. It is permanent from session to session. The ObjectID serves the same purpose, (perhaps it preceded the handle historically) but its a Long integer, and it may change from session to session. OwnerID is the long integer container object. There is no owner handle. Once you have the handle captured to a string, or the ID to a long integer, you convert the string or long to an object with a method from AcadDocument, HandleToObject(string) or ObjectIDToObject(long). I keep Option Strict On to be constantly reminded when I am trying to pass variables from one type to another.

HandleToObject returns a generic object. But if it has a handle, its an AcadObject. So it can be immediately converted to AcadObject. In general, non-graphic elements derive from AcadObject only, and graphic elements derive from both AcadObject and AcadEntity. Everything in the drawing including Blocks collection and ModelSpace derive from AcadObject.

To further identify what type of object you have, use VB method TypeName and TypeOf

Dim obj As Object
Dim acad_obj As AcadObject
obj = acadDoc.HandleToObject(strhandle)
acad_obj = CType(obj, AcadObject)

Debug.Print(TypeName(obj))

Will return the typename and

  Case TypeOf acad_obj Is AcadBlock
  MsgBox("AcadBlock")
  blk = CType(obj, AcadBlock)
  Debug.Print(blk.Name & " " & blk.Count.ToString)

TypeOf will provide a boolean true or false to convert obj to its most specific type so you can use those methods, such as Name of the Block.

To populate a TreeView with an unknown number of levels, a recursive function is made with the parent node passed as one of the parameters.

If you are looking at a Block structure, the top level is a Block, and the embedded blocks are BlockReference.

ModelSpace and PaperSpace are blocks in the Blocks collection, they are also provided as shortcut properties of the Document, so they can be found either way, but usually using the property of the Document.

Button6 is the Block button on the form, it should say Blocks, it gets the entire Blocks collection. clicking on a node in the tree loads one level into ListView.

    Private Sub Button6_Click(sender As Object, e As EventArgs) Handles Button6.Click
        Connect_acad()
        ListView1.Clear()
        Dim acadblks As AcadBlocks
        Dim blk As AcadBlock
        acadblks = acadDoc.Blocks

        Dim strhandle As String
        strhandle = acadblks.Handle
        listview_by_handle(strhandle)

        'start treeview
        TreeView1.Nodes.Clear()

        Dim rootnode As TreeNode
        rootnode = New TreeNode(acadblks.ObjectName)
        rootnode.Tag = acadblks.Handle
        TreeView1.Nodes.Add(rootnode)

        For Each blk In acadblks
            pop_tree(blk, rootnode)
        Next
    End Sub

ListViewByHandle is used to get the whole Blocks collection but its also called by a click event in the Tree to load the item selected. It has to have a fairly comprehensive Select Case to handle entities, blocks, blockrefs and block collection. I don’t try to do any levels in the ListView, so for instance, if you click on a block, it writes the block handle, name etc on the first line, then all the first level constituents under it.

Sub listview_by_handle(strhandle As String)
        ListView1.Clear()
        Dim item As ListViewItem
        Dim obj As Object
        Dim acad_obj As AcadObject
        Dim blk As AcadBlock
        Dim blkref As AcadBlockReference
        Dim acadblks As AcadBlocks

        With ListView1
            .Size = New Size(850, 500)
            .Columns.Add("ObjectName", 180)
            .Columns.Add("TypeName", 180)
            .Columns.Add("ObjectID", 100)
            .Columns.Add("OwnerID", 100)
            .Columns.Add("Handle", 100)
            .Columns.Add("NAME", 180)
            .View = View.Details
        End With

        obj = acadDoc.HandleToObject(strhandle)
        ' Debug.Print(TypeName(obj))
        acad_obj = CType(obj, AcadObject)

        item = New ListViewItem(acad_obj.ObjectName)
        item.SubItems.Add(TypeName(acad_obj))
        item.SubItems.Add(CType(acad_obj.ObjectID, String))
        item.SubItems.Add(CType(acad_obj.OwnerID, String))
        item.SubItems.Add(acad_obj.Handle)

        Select Case True
            Case TypeOf acad_obj Is AcadBlocks
                ' MsgBox("AcadBlocks")
                acadblks = acadDoc.Blocks
                item.SubItems.Add("")
                ListView1.Items.Add(item)

                For Each blk In acadblks
                    item = New ListViewItem(blk.ObjectName)
                    item.SubItems.Add(TypeName(blk))
                    item.SubItems.Add(CType(blk.ObjectID, String))
                    item.SubItems.Add(CType(blk.OwnerID, String))
                    item.SubItems.Add(blk.Handle)
                    item.SubItems.Add(blk.Name)
                    ListView1.Items.Add(item)
                Next

            Case TypeOf acad_obj Is AcadBlock
                'MsgBox("AcadBlock")
                blk = CType(obj, AcadBlock)
                Debug.Print(blk.Name & " " & blk.Count.ToString)

                item.SubItems.Add(blk.Name)
                ListView1.Items.Add(item)

                For Each acad_obj In blk
                    item = New ListViewItem(acad_obj.ObjectName)
                    item.SubItems.Add(TypeName(acad_obj))
                    item.SubItems.Add(CType(acad_obj.ObjectID, String))
                    item.SubItems.Add(CType(acad_obj.OwnerID, String))
                    item.SubItems.Add(acad_obj.Handle)
                    If TypeOf acad_obj Is AcadBlockReference Then
                        blkref = CType(acad_obj, AcadBlockReference)
                        item.SubItems.Add(blkref.Name)
                    Else
                        item.SubItems.Add("")
                    End If
                    ListView1.Items.Add(item)
                Next

            Case TypeOf acad_obj Is AcadBlockReference
                'MsgBox("AcadBlockReference")
                blkref = CType(obj, AcadBlockReference)
                Debug.Print("blkreference " & blkref.Name)

                item.SubItems.Add(blkref.Name)
                ListView1.Items.Add(item)

            Case TypeOf acad_obj Is AcadEntity
                'MsgBox("AcadEntity")
                ListView1.Items.Add(item)

        End Select
    End Sub

pop_tree is the recursive routine that makes the tree. It passes a block type parameter and the node that is parent to the block. It immediately adds a node for the block, then it surveys the block and if the constituent is a blockreference, it recurses using the new node as parent. if the constituent is not a block, it creates a new node and makes the entry. it uses the tag to save the handle so when the tree is clicked, the handle is retrieved, and the listview can be populated from the handle.

    Sub pop_tree(ByVal blk As AcadBlock, ByVal parentnode As TreeNode)
        Dim acad_ent As AcadEntity
        Dim anode As New TreeNode
        anode = parentnode.Nodes.Add(blk.Name)
        anode.Tag = blk.Handle

        For Each acad_ent In blk
            If TypeOf acad_ent Is AcadBlockReference Then
                Dim blkref As AcadBlockReference
                blkref = CType(acad_ent, AcadBlockReference)
                blk = acadDoc.Blocks.Item(blkref.Name)
                pop_tree(blk, anode)
            Else
                Dim newnode As New TreeNode
                newnode = anode.Nodes.Add(acad_ent.ObjectName)
                newnode.Tag = acad_ent.Handle
            End If
        Next
    End Sub

the treeview mouseclick event

    Private Sub TreeView1_NodeMouseClick(sender As Object, e As TreeNodeMouseClickEventArgs) Handles TreeView1.NodeMouseClick
        Dim newSelected As TreeNode = e.Node
        'Dim newSelected As TreeNode = TreeView1.SelectedNode
        Dim strhandle As String
        strhandle = CType(newSelected.Tag, String)
        listview_by_handle(strhandle)
    End Sub
End Class

Autocad Document Object in Visual Studio

Moving up the object model tree, AcadApplication has a property Documents which returns a collection of the open Documents in Autocad. Clicking Application in the clickable Object Model, in the ActiveX Reference Guide, then Documents, leads us to the Documents property. There is some sample code, but no list of properties and methods, which is confusing if we are looking for the Documents object. Sometimes the Property returns an Object with the same name and sometimes the name is different. The property of AcadApplication called Documents returns an AcadDocuments object but you look it up as Documents. Go back to the object model and directly click on Documents, or navigate the tree view in the Objects section to Documents.

clickable object model

The Documents (plural) collection has the minimum number of methods and properties, it is almost the same list as the plain VBA Collection object.
DOCS.ADD starts a new drawing
DOCS.ITEM switches between dwgs already open.
DOCS.OPEN opens an existing drawing.
DOCS.COUNT returns number of open drawings.

Clicking on the ADD Method in the Documents Collection bumps you to the generic ADD page under Methods and you have to find the text that applies to the DOCS object.

ADD takes a name parameter that is the template file to use for a new drawing. if no path is used, it looks in the template folder. you can use either a DWT or a DWG file. if the file is not in the template folder you have to give a complete path.

OPEN is a method in both Documents and Document. It works about the same in each, but Documents has a ReadOnly option that Document does not have.

CLOSE also is a method in both DOCS and DOC. in DOCS it will close all. It has a flag to save changes.

ITEM finds open drawings by either index number or name. The name is the name on the drawing tab plus the extension.

Everything you need to open, create and save drawings is under DOCS or DOC.

the DOCUMENT (single) object is the gateway to all the autocad drawing objects. It has many more properties and methods, including SAVE for previously saved files and SAVEAS for new drawings. It also has a property SAVED to indicate unsaved changes.

Every object in autocad, both the drawing entities and the drawing properties, inherit a prototype object called AcadObject. The actual drawing entities further inherit an object called AcadEntity. In the Objects section of the ActiveX Reference Guide you can find these two prototype objects and their lists of methods and properties under IAcadObject and IAcadEntity. AcadObject has a brief list of methods. AcadEntity has an extensive list of properties and methods. There is also a list of the containers or collection names where these objects are kept – ModelSpace, PaperSpace, Block, SelectionSet and Group. Its the ADDxxx Methods of these primary collection objects that create the drawing. Click on ModelSpace in the object model to see them.