Excel VBA – > Autocad Basics

Microsoft “deprecated” VBA and then Autodesk made the VBA module not a part of their massive install, convincing just about everybody it was no longer a viable platform to write any code. I am not going to write code for other people if i have to go sit at their computer and download and install a 100 mb file and do it on every release of autocad. But two things happened. Microsoft recanted. There must be a million people worldwide who code VBA excel. And it was never necessary to download the VBA module for autocad to run VBA code anyway. The download is just the code editor. The VBA objects are there. The code editor in excel works fine and in fact better because excel can hold data such as parameters, bills of material and cut-lists. For a few years now I have been making it a point not to download the autocad VBA module just to make sure other people could run my programs without adding anything.

To begin writing your Autocad VBA code in excel – start excel. Most of the time you have to add the developer tab to the ribbon. That is done with the File tab, Options, Customize Ribbon. Add the Developer tab. Start the Visual Basic editor. When you save an excel file with visual basic code, save it with XLSM file extension. In the visual basic editor, important first step, you must add a reference for excel to see autocad programming objects. This is under Tools, References. Look for Autocad Type Library. Move it up in the list as high as it will go.

2018-01-28_2.jpg

now you need a standard module to connect to autocad. insert a module, add two public variables at the top.

Public acadApp As AcadApplication
Public acadDoc As AcadDocument

start your sub procedure by typing
sub connect_acad()
and hit return

generally you will want to start autocad yourself the way you always do. if autocad is running the vba code to obtain a reference to it is

Set acadApp = GetObject(, "AutoCAD.Application")

notice we use our global variable. type this in to your sub procedure. I find this works fine but most online help shows it with a version dependent string like this.

Set acadApp = GetObject(, "AutoCAD.Application.22")
22 is the version that autocad is using to designate version 2018

to demystify this, what is GetObject, why is there a comma, where does this come from, open up the Object Browser. you can double click the word GetObject, right click, and pick Definition. if you dont find it that way, search the VBA library, top box, for GetObject –

The Object Browser shows its parent, VBA.Interaction and it shows the parameter list PathName comma Class. Right click on GetObject in the Object Browser and click Help to open MS online help.

2018-01-28_5.jpg

2018-01-28_3.jpg

If Autocad is not running, GetObject will return an error. The code to start autocad in VBA is CreateObject or New AcadApplication. They both work for me, just as the version independent and dependent string both seem to work equally well.

We need to handle the error, then deal with either autocad running or not. Autodesk has the logic and a file to do it here.

https://knowledge.autodesk.com/search-result/caas/CloudHelp/cloudhelp/2018/ENU/AutoCAD-ActiveX/files/GUID-73EC319D-9D7D-43FF-91B7-78CF36284028-htm.html

this file is also in the Autodesk AutoCAD 2018: ActiveX Developer’s Guide, or whatever year you have, which is file acad_aag.chm somewhere in your autocad installation. find that and also acadauto.chm and copy them where you want them.

First we have to add the essential statement
acadApp.Visible = True
otherwise we will not see any autocad.

Autodesk has the commands and logic but not the best form. We have to make some improvements. The code to start autocad needs to be in its own sub procedure with the code to reference the active drawing. We want to call connect_acad once and we want to obtain a global variable for the active drawing.

When autocad opens, it probably opens to a blank drawing, or maybe its already open to a drawing, but we have to get that drawing into a VBA object. Generally I want it to be a blank drawing because I am going to use it. The connect_acad sub procedure will not run until we want it to, so it just gets the activedocument which is a property of AcadApplication. Those are our two global variables, the application and the active drawing, and active drawing is the main one we use.

Put connect_acad in your module as a stand-alone. Anytime you want to write a program in excel vba to draw or interact with autocad, the first thing in the program is

call connect_acad

at that point you have a global variable AcadDoc that you use for all of autocad’s vba objects.

here is the full acad_connect

Option Explicit
 
 Public acadApp As AcadApplication
 Public acadDoc As AcadDocument
 
 
 Sub Connect_Acad()
    
    On Error Resume Next
        
    Set acadApp = Interaction.GetObject(, "AutoCAD.Application")
    'Set acadApp = Interaction.GetObject(, "AutoCAD.Application.22")
    'both statements above behave without any discernible difference
    
    If Err Then
        Debug.Print "ERROR " & Err.Number
        Debug.Print Err.Description
        Debug.Print "starting autocad"
                
        Err.Clear
                      
         Set acadApp = New AcadApplication
         'Set acadApp = Interaction.CreateObject("AutoCAD.Application.22")
         'both statements above behave without any discernible difference
        
        'essential statement
        acadApp.Visible = True
        
        If Err Then
             MsgBox Err.Description
             Exit Sub
        End If
    End If
    
    Debug.Print "Now running " + acadApp.Name + " version " + acadApp.Version
         
    Set acadDoc = acadApp.ActiveDocument
        If acadDoc Is Nothing Then
            Set acadDoc = acadApp.Documents.Add
        End If
  
    If acadDoc.ActiveSpace = 0 Then
       acadDoc.ActiveSpace = 1
    End If
   
End Sub

in bricscad, try these (i dont have bricscad loaded currently but these worked a couple releases back)

Set acadApp = GetObject(, "BricscadApp.AcadApplication")

Set acadApp = New AcadApplication

using autodesk’s example to draw a single line would be modified like this. I made no other changes except adding connect_acad

Sub testline()

Call Connect_Acad

Dim lineObj As AcadLine
Dim startPoint(0 To 2) As Double
Dim endPoint(0 To 2) As Double
startPoint(0) = 1
startPoint(1) = 1
startPoint(2) = 0
endPoint(0) = 5
endPoint(1) = 5
endPoint(2) = 0

Set lineObj = acadDoc.ModelSpace.AddLine(startPoint, endPoint)
ZoomAll

End Sub

Using this method, any code on autodesk help website that uses Thisdrawing.object can be run from excel by adding connect_acad at the top and replacing the word Thisdrawing with AcadDoc.

Advertisements

Specific method for parametric drawing programs

The hard part of coding parametric drawing program whether in lisp or VBA is managing the large number of points. The program turns into many lines of hard to read data apparently randomly named. A sketch has to be made with points labeled and equations or formulas entered. It all might make sense during the coding, but probably won’t a few weeks later when a change has to be made even if the sketch(s) is found. It won’t be obvious how the points are calculated or why lines are drawn from pt7 to pt21 to pt3. There is no one right way but I have recently worked on both lisp and vba programs and have some specific but not comprehensive suggestions. This is a special theory for creating the xy data but not a general theory for the entire program.

There are two basic ways to manage your drawing subroutine. It can accept points or xy data. Try both ways. Both methods need xy data.

In Lisp I use Visual Lisp objects rather than the “command” method. The object method can draw directly in to a block definition, and it can directly change the layer property. It requires a point object, but that can be created and passed as a parameter or the xy data can be passed and the point created in the subroutine.

For lisp I made a point creation routine and passed points to the subroutine which runs the Addline method.

(defun pt ( x y ) (vlax-3d-point x y 0))

In a very simple box example this gets called as

(setq pt1 (pt 0 0) pt2 (pt L 0) pt3 (pt L W) pt4 (pt 0 W))

Then the line routine would be

(defun linep (pt1 pt2 obj lyr / lineobj)
(setq lineobj (vla-AddLine obj pt1 pt2))
(vla-put-layer lineobj lyr) )

And be called as

(linep pt1 pt2 ms "hidden")

Or you could pass xy data

(defun line (x1 y1 x2 y2 obj lyr / pt1 pt2 lineobj)
(setq pt1 (vlax-3d-point x1 y1 0)
pt2 (vlax-3d-point x2 y2 0))
(setq lineobj (vla-AddLine obj pt1 pt2))
(vla-put-layer lineobj lyr) )

In VBA every variable has to be declared previous to use, so you might lean towards passing xy data. Assume you want to draw a notched rectangle and make it a polyline. You make a sub specifically for this purpose. After setting the xy data coordinates, any six vertex closed polyline can be drawn with

Call p6_box(x1, y1, x2, y1, x2, y2, x3, y2, x3, y3, x1, y3)

Sub p6_box(p1 As Double, p2 As Double, p3 As Double, p4 As Double, p5 As Double, p6 As Double, _
p7 As Double, p8 As Double, p9 As Double, p10 As Double, p11 As Double, p12 As Double)

Dim objent As AcadLWPolyline
Dim pt(0 To 11) As Double
pt(0) = p1: pt(1) = p2
pt(2) = p3: pt(3) = p4
pt(4) = p5: pt(5) = p6
pt(6) = p7: pt(7) = p8
pt(8) = p9: pt(9) = p10
pt(10) = p11: pt(11) = p12
Set objent = acadDoc.ModelSpace.AddLightWeightPolyline(pt)
objent.Closed = True
Set obj_Acad_Entity = objent
End Sub

This makes no sense without a sketch but the sub p6_box can draw any closed polyline with 6 points configured any way you need it.

2018-01-13_2.jpg

Our notched box is L X W with an A X B notch, drawn with the lower left corner at 0,0. There are 3 X coordinates and 3 Y coordinates.
X1=0 , X2=L-B , X3=L
Y1=0 , Y2=A , Y3=W

You can turn this box around any way you wish, move the notch to the middle, put a hole in the middle. Just label xy coordinates as needed in order from the origin. This is how you organize your xy data without duplication in a straightforward way. Sometimes its convenient to also label points, sometimes its not required, but the xy data must always be figured from the parameters as the first step.

In VBA we would probably draw in counterclockwise order.

Call p6_box(x1, y1, x2, y1, x2, y2, x3, y2, x3, y3, x1, y3)

Now it should make sense. The xydata starts at the origin. Subroutines can be written so declared point variables are not required, or required. If you have a lot of sub-routines, just declare your x1, x2, etc as public to avoid re-declaring.

In programming 101 they strongly suggest that your subroutines be simple and single purpose. Just about every autocad parametric program I have seen or written has been a mess at the actual geometry creation level. For instance in this example, the parameters A and B, L and W may need to have complicated formulas behind them. Put those upstream of the actual sub-routine that draws the geometry. Make the geometry creation as simple as possible. Pass the actual parameters if possible, do not develop them. Interface is top down thinking, but geometry is bottom up.
Such as

Sub draw_notch_box(W As Double, L As Double, A As Double, B As Double)
x1 = 0
x2 = L - B
x3 = L
y1 = 0
y2 = A
y3 = W
Call p6_box(x1, y1, x2, y1, x2, y2, x3, y2, x3, y3, x1, y3)
End Sub

You will be able to read that next year if you remember that xy data starts at the origin.

Make Block with ActiveX

code in VBA and Visual Lisp for comparison,
connect, establish application and document objects, draw line in modelspace, add a new block, draw line in the block, insert the block.


Public acadApp As AcadApplication
Public acadDoc As AcadDocument
Public mspace As AcadModelSpace

Sub demo()

'autocad is running and doc open
Set acadApp = GetObject(, "AutoCAD.Application")
Set acadDoc = acadApp.ActiveDocument
Set mspace = acadDoc.modelspace

Dim lineobj As AcadLine
Dim pt1(0 To 2) As Double, pt2(0 To 2) As Double
pt1(0) = 2: pt1(1) = 1: pt1(2) = 0
pt2(0) = 6: pt2(1) = 7: pt2(2) = 0

'Set lineobj = acadDoc.modelspace.AddLine(pt1, pt2)
Set lineobj = mspace.AddLine(pt1, pt2)

Dim pt3(0 To 2) As Double
pt3(0) = 0: pt3(1) = 0: pt3(2) = 0

Dim blks As AcadBlocks
Set blks = acadDoc.blocks

Dim blkdef As AcadBlock
Set blkdef = blks.Add(pt3, "Blk_Demo2")

Set lineobj = blkdef.AddLine(pt1, pt2)
Set lineobj = blkdef.AddLine(pt2, pt3)

Dim blkref As AcadBlockReference
Set blkref = mspace.InsertBlock(pt3, "Blk_Demo2", 1, 1, 1, 0)

End Sub
(vl-load-com)

   (setq acadApp (vlax-get-acad-object))

   (setq acadDoc (vla-get-ActiveDocument acadApp))

   (setq mSpace (vla-get-ModelSpace acadDoc))

   (setq pt1 (vlax-3d-point 1 1 0)
         pt2 (vlax-3d-point 7 5 0))
    (setq lineObj (vla-AddLine mSpace pt1 pt2))

    (setq pt3 (vlax-3d-point 0 0 0))

    (setq blks (vla-get-blocks acadDoc))

    (setq blkdef (vla-Add blks pt3 "Blk_Demo1"))
    (setq lineobj (vla-AddLine blkdef pt1 pt2))
    (setq lineobj (vla-AddLine blkdef pt2 pt3))

    (setq blkref (vla-insertblock mspace pt3 "Blk_Demo1" 1 1 1 0 ))

side by side,

this is an intuitively different process than creating a block by manual drafting methods. the graphic methods such as addline are the same for drawing in modelspace and drawing in blockspace. modelspace is a type of block, autodesk tells us. we create a block with nothing in it, then use the object returned just as we obtain the modelspace object to put objects in modelspace.

this example is very similar to the autodesk activeX Insertblock help page. http://help.autodesk.com/view/OARX/2018/ENU/?guid=GUID-17F86FDD-B7FC-4F43-9F16-B4958F73A66D