CAD Clinic: AutoCAD Commands in VB .NET14 Jun, 2005 By: Mike Tuersley Cadalyst
Use .NET Editors to Create Simple AutoCAD commands
Welcome back! This month we're going to create two simple AutoCAD commands in Visual Basic .Net. If you have not already read last month's column discussing .NET editors, please do so before continuing with this article. With the commands that I will create here, you can use any editor -- including Windows' NotePad -- because no graphics are involved. I will use the free .NET editor SharpDevelop by SharpDevelop.
For this article, we will recreate the classic "Hello World" example and throw the text into AutoCAD's command line and into an MText object. The code is derived from the Autodesk sample available in the AutoCAD 2006 install, in the AutoCAD 2006 ObjectARX toolkit and from the Autodesk Web site.
To begin, open SharpDevelop and create a new combine -- a combine is equivalent to a new project in Microsoft Visual Basic. In the next dialog box, select a Class Library combine (figure 1).
Figure 1. Creating a new project in SharpDevelop.
Now add the references to the project just like with Visual Basic 6 or VBA. An important difference exists between Microsoft's Visual Studio products and SharpDevelop here. After selecting to add a reference in VS, you must browse to AutoCAD's installed directory and select the managed libraries: ACDBMGD.DLL and ACMGD.DLL. Don't select the AutoCAD 2006 type library in the COM tab! That would create a COM InterOp project similar to VB6-based programming. This project will be a .NET managed project, so you need to hook into the MGD files (please refer to last month's column for information regarding managed code). This step is easier in SharpDevelop because the two files are selected through the GAC tab (figure 2).
Figure 2. Adding references in SharpDevelop
Now you should have the code window open for SharpDevelop. Its environment is similar to Visual Studio's. For more information, please refer to SharpDevelop's Help file. Okay, let's look at the code to accomplish the first "Hello World!" example in VB.NET. The easiest way is for me to show the code then describe it:
Imports System Imports System.Runtime.InteropServices Imports Autodesk.AutoCAD.Runtime Imports Autodesk.AutoCAD.ApplicationServices Imports acadApp = Autodesk.AutoCAD.ApplicationServices.Application Public Class HelloWorld
_ Public Sub HelloCommand() acadApp.DocumentManager.MdiActiveDocument.Editor.WriteMessage( _ vbNewLine & "Hello World!" & vbNewLine) acadApp.UpdateScreen() End Sub End ClassM
Starting from the top, the first block of code is the Imports statements. Besides adding references to the assemblies (references) in .NET, you specifically state which assemblies' namespaces are to be used. Think of a namespace as an object within the reference that you want to access. This step also saves typing within your code. For example, in the Autodesk examples, you'll see them call the MdiActiveDocument.Editor like this:
That's a lot of code to type in if you don't need to.
Also notice that within the Imports block, I also create a global variable AND set it in the same statement. This is a great feature of .NET where you can declare and set variables all in one line such as:
In VB6-based code:
Dim sText As String sText = "Hello World"
In .NET code:
Dim sText As String = "Hello World"
The next block of code is our class object. Just as in VB6-based programming, our DLL needs a class so it can be called from AutoCAD. Then I have the actual subroutine, HelloCommand, where I write the message to AutoCAD's command line. Notice the subroutine has a descriptor attached to it:
This is all we need to make our subroutine accessible from within AutoCAD! No more calling it through VBA or writing a LISP wrapper to access it. That functionality alone is worth the learning curve of .NET.
Imports System Imports Microsoft.VisualBasic Imports acadApp = Autodesk.AutoCAD.ApplicationServices.Application Public Class HelloWorld <Autodesk.AutoCAD.Runtime.CommandMethod("HELLO")> _ Public Sub HelloCommand() acadApp.DocumentManager.MdiActiveDocument.Editor. WriteMessage(vbNewLine & _ "Hello World!" & vbNewLine) acadApp.UpdateScreen() End Sub End Class
To test this program, compile it. Then fire up AutoCAD 2006, type in NETLOAD and load the DLL you just created. Once it loads, type HELLO into AutoCAD's command prompt and "Hello World!" should echo back after you press the Enter key.
Figure 3. Using NETLOAD in AutoCAD and the end result of Hello World
No unloading option exists with .NET assemblies, so AutoCAD needs to be shutdown if you want to load the DLL a second time. This is important to note in case, for example, the DLL did not work and you need to change it and re-test it.
Assuming that worked, let's change the program to insert the "Hello World!" text into the current drawing as MText. The code to accomplish this is going to look like:
Imports System Imports System.Type Imports System.CLSCompliantAttribute Imports System.Reflection Imports System.Runtime.InteropServices Imports Microsoft.VisualBasic Imports Autodesk.AutoCAD.Runtime Imports Autodesk.AutoCAD.ApplicationServices Imports Autodesk.AutoCAD.DatabaseServices Imports Autodesk.AutoCAD.EditorInput Imports acadApp = Autodesk.AutoCAD.ApplicationServices.Application Public Class HelloWorld <Autodesk.AutoCAD.Runtime.CommandMethod("HELLOTEXT")> _ Public Function HelloTextCommand() Dim acadMText As MText Dim acadBT As BlockTable Dim acadBTR As BlockTableRecord Dim acadDB As Database = HostApplicationServices.WorkingDatabase Dim acadTrans As Transaction acadTrans = acadDB.TransactionManager.StartTransaction() Try acadBT = trans.GetObject(acadDB.BlockTableId, _ Autodesk.AutoCAD.DatabaseServices.OpenMode.ForRead) acadBTR = trans.GetObject(acadBT(acadBTR.ModelSpace), _ Autodesk.AutoCAD.DatabaseServices.OpenMode.ForWrite) acadMText = New MText() acadMText.Contents = "Hello World!!" acadBTR.AppendEntity(acadMText) acadTrans.AddNewlyCreatedDBObject(acadMText, True) acadTrans.Commit() Catch acadTrans.Abort() Finally If Not isnothing(acadTrans) Then acadTrans.Dispose() If Not isnothing(acadMText) Then acadMText.dispose() If Not isnothing(acadBT) Then acadBT.dispose() If Not isnothing(acadDB) Then acadDB.dispose() End Try End Function End Class
Starting at the top, again, I added some more calls to the Imports section. Because this required drawing the text, I had to go into AutoCAD instead of just using its command line. After that, I declared my class object and my subroutine. Here, I changed the sub name and the AutoCAD command so this code can co-exist in a project alongside the first example.
Communicate with AutoCAD
Next, is the actual communication with AutoCAD, which is extremely important! You'll use this same structure again and again as you do more managed projects, so it is worth delving into in more detail.
With unmanaged, COM-based code, I could access the appropriate drawing space (model or layout) and add the MText object. With managed code however, I am inserting an MText object directly into the drawing's database. In order to complete this task, I must go through the AutoCAD Transaction object just as if this were a program I was writing that talked to a Microsoft Access or SQL database. This process can be stepped out like so:
- Start a Transaction
- Connect to the drawing's database
- Connect to the drawing database's Block table
- Create a record in the Block table
- Save or Cancel the transaction
The other new concept in this block of code is the Try-Catch-Finally loop in .NET. The Try statement is a hook to .NET's garbage collection and handles error trapping for us. In .NET, we don't need the old "On Error Resume" statements, and you should not use them because they bypass the automatic garbage collector in .NET. The way this structure works is if any error occurs within the code following the Try command, it is sent to the Catch statement. Multiple Catch statements can handle different errors just like the COM approach of testing for error codes in an error handler. For example, a typical Catch statement looks like: CATCH ex As Exception. The Finally command is optional. If included, the program goes there regardless of whether an error occurs or not. In this example, I want to clear out my object variables no matter what happens. As you can see, I call the individual object's Dispose method to terminate the object reference instead of setting the object to Nothing as in VB6. If I were to set an object to Nothing in .NET, the reference to the object would still exist -- it would just be nothing.
Source Code Notes
If you are just starting with .NET, the source code for this month is complete, but you may need to remove and reattach the references to the MGD files. If you are using another editor instead of SharpDevelop, the only file needed in the source code is the NEW CLASS.VB file. From Microsoft's Visual Studio, create a new class library project and then add the file to it. For other editors, just use this file as you need. The nice feature with .NET is that the VB file is ASCII text until the time it is compiled, so it can be opened with a program as simple as NotePad.
Kick It Up A Notch!
Using this code as an example, explore what else you can create in the managed world. Remember to use the Object Browser to walk yourself through the API as you do more complicated tasks. Next month, I will take both these examples and do them again in C#. See you then!