Using XREFs and XREF Best Practice

XREFs can be an extremely helpful tool at your disposal when used well. However when used badly they can be a nightmare. In this article I am going to go through how to use XREFs and talk about some best practice tips to get the most out of your XREFs. Also I’ll talk about some common pit-falls that often cause problems.

What is an XREF?

If it wasn’t already obvious to you, XREF stands for external reference. As such, these are drawing files that are used as external references in our drawing. Other names for XREFs include references, overlays, attachments and model files. No matter what the name, the concept is the same. The external file is inserted into the current drawing as a reference. If you want to edit the geometry of the XREF, you’ll need to open the referenced file and edit that.

Basic Usage

We can insert an XREF by simply entering XREF into the command line. This brings up the the External References dialog box. In the top left there is a button for attaching a DWG file. Click this button, and you will be prompted to select a DWG file to insert. The insertion point for the XREF will always use the coordinate 0,0 in the XREF as the base-point in a similar fashion to how blocks work.

The External References dialog lists the currently loaded XREFs and displays details for each. Here is a good place to look to find any problems with your XREFs, such as XREF paths that might have changed. You can update the XREF paths from here by overwriting the “found at” path, or by selecting the ellipsis (…) in this field and browsing to the drawing. If you seem to be unable to edit this, it is likely that the reference you are trying to edit is a nested reference. This means that the drawing is actually an XREF within another drawing, and has not been directly referenced to this drawing. In other words, it is an XREF within an XREF. If this is the case you’ll need to edit the path within the first XREF, and then refresh that XREF within the current drawing.

While the External References dialog can be convenient, for simply inserting an XREF you do not need to open this dialog. Using the XATTACH command, you can invoke the command that would be used by clicking the DWG button on the XREF dialog.

Best Practice

What to use XREFs for

XREFs should be used for content that could potentially be displayed on many drawings. Having multiple copies of the same thing is just a pain to deal with. Ok, you can copy easily enough from one drawing to the other, but you have to remember to do that every single time you make any change to either drawing. The solution is to use an XREF. Put all the information in the one drawing and insert that XREF into all the drawings that require it. Essentially, XREFs can become a kind of external modelspace for your drawings, saved in a central location. You can also logically separate aspects of the model so that each XREF has its own distinct purpose. This is a great way to add some structure to you drawings, and minimise any headaches you might face later with discrepancies between drawings.

Breaking your modelspace down into smaller XREFs is also a great way to reduce file size. I work under the premise that the bigger the drawing file size, the higher the chance that it’ll crash my PC. As such I try to keep individual drawings to below 5mb. Sometimes this is not practical, but mostly it is.

Also, be careful of surveys and other mapping information such as Ordnance Survey maps. While these are of course essential, avoid putting these directly into your drawings like the plague. These should ALWAYS be an XREF, unless you actually want AutoCAD® to crash 🙂

Attachments vs Overlays

XREFs can be inserted as two different types; an attachment or an overlay. While both may appear to achieve the same thing, there is a subtle difference between them. If you insert an XREF as an attachment, this means that any other drawings that XREF the current drawing will also show the nested XREF as well. For example, Model.dwg contains the XREF Outline.dwg as an attachment. If Drawing1.dwg then XREFs Model.dwg in, Outline.dwg will also be shown as a part of Model.dwg.

Conversely, overlays allow you to insert as many XREFs as you want, but prevents them from being nested. So in the example above, if Outline.dwg were inserted as an overlay, it would not be shown when Model.dwg is XREFed into Drawing1.dwg.

So which do you use? Well, like most things it depends what you’re doing. Generally speaking the preferred default option is to use overlays, because this prevents the possibility of having a reference loop, and is generally the more lightweight option. However, if you have much to gain from having XREFs that contain other XREFs, clearly an attachment is the way to go.

Where should XREFs be saved?

This is a good question – they have to go somewhere, and having them scattered all over the place so that you don’t know what’s what is not good at all. Generally speaking, I create a folder called xrefs, and put them all in there. I sort of consider the xrefs folder as the modelspace of all my drawings, and I treat it accordingly.

I have the luxury of working on projects that aren’t massive, so if you’re working on huge projects you’ll certainly need some other structure to accommodate the scope of the project. It would be no good having hundreds and hundreds of drawing files all saved in one folder named xrefs. For this, you’d probably have to set up zones, and subcategorise your xrefs by zone. This will have to be done on a project by project basis, and agreed with the others working on the project before starting.

Pit Falls

I’m going to kick off the pitfalls with VISRETAIN, a system variable that has been the bane of many a AutoCAD® user. It can easily catch you out as well. VISRETAIN controls how layers within XREFs behave. With VISRETAIN set to 1, the layer state of XREFs are remembered. So when you make some intricate changes to your XREF layers to ensure it plots a certain colour for example, this will be remembered. Set VISRETAIN to 0 and it’s another story – you’ll close the drawing, the layer changes you made will be lost, and you’ll have no idea it happened until you open the drawing again, and (maybe) spot the mistake. If you never ever want this to be an issue again, put this in acaddoc.lsp, which ensures VISRETAIN is set to 1 each time a drawing is opened:

(setvar “VISRETAIN” 1)

Next, I’ll talk about broken XREFs. A broken XREF is what we call an XREF with a path that no longer resolves to the .DWG file that it originally pointed to, and the file cannot be found. This is usually the result of an element of the path being renamed, such as the folder or the .DWG file itself. As noted earlier, you can update the path for the XREF from the XREF dialog, and modifying the “found at” path.

An additional problem can be to do with drive paths. Say you have an XREF that resides in W:\CAD\Projects\xrefs, but you also have a Z drive mapped such that Z:\Projects\xrefs refers to the same folder. If the XREF was originally loaded using the W:\CAD\Projects\xrefs path, then AutoCAD® usually expects the drawing to be on the W drive. This can be a temperamental issue, but I have often found that AutoCAD® will prevent you from re-loading an XREF from what appears to be another drive.

If you or someone else has renamed a folder, you’ll notice that many, if not all of your XREFs will have a broken path. Then you’re faced with the somewhat daunting task of updating all the XREF paths in your whole drawing set… For this, look into using the Reference Manager tool provided with AutoCAD® – its really useful, but I’ll save the details for another post.

The final problem is that with great power comes great responsibility! Although it is great to be able to update all your drawings in one go, equally you can screw the whole set up just as easily. BE CAREFUL. And regularly take a backup of your XREFs folder so that if you make a huge mistake, you can roll back to a previous version.

 

So there we go – XREFs. Use them, and use them wisely.

Will

P.S. Don’t forget to subscribe below – I’m nearly at 200 subscribers!

AutoCAD®, VB and Mathematics

If you want to become an expert AutoCAD® script writer, at some point you’re going to need to know a bit about maths, or at least have the capacity to learn. It doesn’t have to calculus, but stuff like Pythagoras theorem and trigonometry are very much your friend in a geometric environment such as AutoCAD®. Cringing yet? Don’t worry – often it’s not as difficult as it seems.

I’ve been asked how to replicate the measure command along an arc – there is no real simple answer (that I can think of). The way I would do it is through actually calculating the points in 2D space. This may sound tricky, but languages such as VB really are conducive to this kind of work, and you may be surprised how simple this actually is.

So for the purposes of this post I will be showing you how to calculate and draw a circle of points using VBA. Obviously an arc is merely just part of a circle, so isn’t really any different. Although we’re using VBA for convenience, the rationale behind the exercise will work in any programming language.

Step One – Get it clear in our minds

Logically, to define a circle in 2D space, you need to know the centre for the circle, and the radius. It’s no coincidence then that this is exactly what is required by the CIRCLE command in AutoCAD.

To define a point on the circumference of that circle then – if you have defined your circle, all you need is an angle. From that angle, we can logically derive the point on the circle at that angle. So in our minds at least, we know how to define a point on the circumference of a circle using a centre for the circle, a radius, and an angle.

Now we have to convert that logic into code.

Step Two – Understand Sine and Cosine

One of the key things that will be at work here will be the use of the trigonometric functions sine and cosine. I think it is worth delving into what these actually mean, as understanding this can be very helpful for this kind of work. In fact, when I was at school I was taught parrot-fashion how to use SOH-CAH-TOA to work out which trig function to use for which situation. I’ve never been a fan of that kind of “learning”. I like to understand the underlying principles so that I can apply them to any situation, but sadly I was never taught in that way. My own curiosity led me to figuring out the diagram below for myself, and it really enhanced my ability to understand trigonometry, and what sine and cosine actually are.

Consider the image below:


Assuming a hypotenuse (the angled lines coming from the centre) length of 1, sine is the length of the deflection in the Y axis for a given angle. So in the example above, a line drawn at 30°  a length of 1, will produce a deflection in the Y axis of 0.5. Sine 30 = 0.5. It’s as simple as that.

Cosine is equally as simple, but rather than the deflection in the Y axis, it will return the deflection in the X axis. So, Cosine 30 = 0.866.

This is how we are able to determine the length of sides in a triangle from angles through trigonometry. Lets assume the hypotenuse is actually 4. The ratio between the sides is still the same, so the calculation to work out the vertical edge of the triangle (the deflection in the Y axis) is as simple as 4 * Sin 30. We multiply by the length of the hypotenuse to scale the side length to the correct size. Simples.

Step Three – Write the code

I think we’re ready for some code. Below is a function that accepts the various inputs we have identified and will draw a point in modelspace.

Sub drawPointOnCirc(xPos As Double, yPos As Double, radius As Double, angle As Double)
Dim insertPoint(2) As Double
Dim xOffset As Double
Dim yOffset As Double
Dim angleRadians As Double
Const PI As Double = 3.14159265358979

'Convert degrees to radians, as required by VBA trig functions
angleRadians = 2 * PI * angle / 360

'Calculate the X and Y offset from the centre of the circle
xOffset = Cos(angleRadians) * radius
yOffset = Sin(angleRadians) * radius

'Add the circle centre to the offset values, to get the absolute
'position of the point on the circumference of the circle.
insertPoint(0) = xPos + xOffset
insertPoint(1) = yPos + yOffset

ThisDrawing.ModelSpace.AddPoint insertPoint

End Sub

This function is actually really useful to us now. We are able to draw a point in modelspace that represents a location on the circumference of a circle. Now, if we were to call the function in the manner below:

Sub main()
Dim n As Double
For n = 1 To 120
drawPointOnCirc 10, 10, 3, n
Next
End Sub

this would draw 120 points at 1 degree intervals along an arc. The circle centre is defined at 10,10 with a radius of 3, which is what the arc should show.

And that’s it!

Hopefully this post has shown you how a little bit of maths can be helpful – the production and reuse of functions like this is really helpful to building up more complex applications… have fun, and as always, feel free to get in touch if you have any problems.

And please subscribe below if you want more tips like this!

Will

How to Control AutoCAD® From a Standalone Executable

I’ve had a few requests recently to do a post on creating executables that control AutoCAD®. Though generally it is better to have tools well integrated into AutoCAD® via the use of a NETLOADed dll for example, sometimes an exe can be an elegant solution.

The general concepts are fairly straight forward.

  1. Check for a running instance of AutoCAD®, and create a reference to it. If it is not running, launch AutoCAD®, and create a reference to it.
  2. Use COM to control AutoCAD

When I started that list, I expected it to be longer – but essentially that’s it. If you’re more of a VBA user, you’ll be very pleased to hear that as we’re using COM, you can pretty much re-use all the VBA code you ever learned.

So, lets draw a basic shape. Something like below:

random shape

Ok – this can easily be handled by a polyline, so this is what our exe will do.

First things first though. Create a new standalone exe project in VB. You can use VB.NET, or VB6 for this. You’ll not need to worry about targeting any specific .NET frameworks or anything like that. As we are using COM we are completely sidestepping those issues.

So I’m going to go ahead with VB6; a bit old-school, but it illustrates the point well. New Project > Standard EXE.

Firstly, we need to create a reference to AutoCAD® in our code. To do this, we need to load the relevant AutoCAD® types and libraries into our exe project. So, go to Project > References. From here you’ll want to select the AutoCAD® type libraries you want to use, depending on which version of AutoCAD® you’re using.

Once loaded, we can create variables that are typed for AutoCAD®, and we are now able to write our code. For the purposes of this example we’re not really interested in using the Form properly, we’re only really using it as a container for our code in the Load event:

Private Sub Form_Load()
Dim ACAD As AcadApplication 'Create ACAD variable of type AcadApplication
'On Error Resume Next 'This tells VBA to ignore errors
Set ACAD = GetObject(, "AutoCAD.Application") 'Get a running instance of the class AutoCAD.Application
'On Error GoTo 0 'This tells VBA to go back to NOT ignoring errors
If ACAD Is Nothing Then 'Check to see if the above worked
Set ACAD = New AcadApplication 'Set the ACAD variable to equal a new instance of AutoCAD
ACAD.Visible = True 'Once loaded, set AutoCAD® to be visible
End If

Dim llCorner As Variant
llCorner = ACAD.ActiveDocument.Utility.GetPoint(, "Pick the lower left corner for the shape")

'Draw shape in terms of the lower left corner
Dim coords(11) As Double
'lower left corner of shape
coords(0) = llCorner(0)
coords(1) = llCorner(1)
'lower right corner of shape
coords(2) = llCorner(0) + 100
coords(3) = llCorner(1)
'upper right corner of shape
coords(4) = llCorner(0) + 100
coords(5) = llCorner(1) + 30
'right hand corner of semicircle
coords(6) = llCorner(0) + 70
coords(7) = llCorner(1) + 30
'Draw the semicircle as a straight line for now. We will set the bulge on this segment later.
'left hand corner of semicircle
coords(8) = llCorner(0) + 30
coords(9) = llCorner(1) + 30
'upper left corner of shape
coords(10) = llCorner(0)
coords(11) = llCorner(1) + 30

'With the coordinates defined, use these to create a new polyline
Dim poly As AcadLWPolyline
Set poly = ACAD.ActiveDocument.ModelSpace.AddLightWeightPolyline(coords)
'Set the bulge of segment 4 of the polyline (ie, index 3) to a value of -1
poly.SetBulge 3, -1
'Close the polyline
poly.Closed = True

'End application
Unload Me

End Sub

So that’s how to draw this shape using a VB6 standalone. The method would be very similar for VB.NET, but you’ll need to take a slightly different approach when accessing the ThisDrawing object. This is discussed in my Introduction to VB.NET in AutoCAD.

Hope this helps someone, and I encourage you to join the hordes of people that have subscribed by filling out an email address below!

Will

Export Points from AutoCAD® to CSV file using VBA

Today I’m going to show you how to use a bit of simple VBA to export data to a CSV file. It’s really simple, and although there are other ways of achieving the same thing, this way allows great flexibility, and is very versatile.

Firstly, I’ll set up a drawing with a few points in it. These points will be what we will export:

Screenshot Of Points

The next step is to open the VBA editing window using the VBAIDE command. Of course, if you’re using AutoCAD® 2010+ you’ll need to download the VBA add-on. Once open, right click in the project explorer window and create a new module.

Now for the code. I was going to explain the code, but as I’ve commented it pretty well, I don’t really think I need to. Take note of the part about the FileSystemObject, as you’ll need to follow the instructions before the code will work. Do ask if you have any other questions.

Option Explicit

Sub ExportPoints()
    'Declare variables
    Dim currentSelectionSet As AcadSelectionSet
    Dim ent As AcadEntity
    Dim pnt As AcadPoint
    Dim csvFile As String
    Dim FSO As FileSystemObject
    Dim textFile As TextStream
    
    'Create a reference to the selection set of the currently selected objects
    Set currentSelectionSet = ThisDrawing.ActiveSelectionSet
    
    'Check if anything is selected, and give exit with a warning if not
    If currentSelectionSet.Count = 0 Then
        ThisDrawing.Utility.Prompt "There are no currently selected objects. Please select some points to export, and run this command again." & vbNewLine
    End If
    
    'Use a For Each statement to look through every item in CurrentSelectionSet
    For Each ent In currentSelectionSet
        'In here, ent will be one of the selected entities.
        
        'If ent is not a point object, we should ignore it
        If TypeOf ent Is AcadPoint Then
            'Only points will make it this far
            
            'Now that we know we are dealing with a point,
            'we can use the specific AcadPoint type of variable.
            Set pnt = ent
            'You'll notice that after doing this, you have more
            'intellisense methods when you type "pnt."
            
            'Add a line to the string variable csvFile.
            'We are concatenating two numbers together with a comma in between,
            'and adding a new line character at the end to complete the row.
            csvFile = csvFile & pnt.Coordinates(0) & "," & pnt.Coordinates(1) & vbNewLine
            
            'Saying that csvFile = csvFile & whatever is a useful
            'way to repeatedly add to the end of a string variable.
            
        End If
        
    Next
    
    'Write the contents of the csvFile variable to a file on the C:\ with the same name
    
    'FileSystemObjects are really useful for manipulating files
    'But, you'll need a reference to the Microsoft Scripting Runtime in your VBA project.
    'Go Tools>References, and select the Microsoft Scripting Runtime.
    
    'Create a new File System Object
    Set FSO = New FileSystemObject
    
    'Using FSO.CreateTextFile, create the text file csvFile.csv,
    'and store a reference to it in the variable textFile
    Set textFile = FSO.CreateTextFile("C:\csvFile.csv")
    
    'Write the string variable csvFile to textFile
    textFile.Write csvFile
    
    'Close textFile, as we are finished with it.
    textFile.Close
    
    'Alert the user that the file has been created
    ThisDrawing.Utility.Prompt "Points have been exported to C:\csvFile.csv" & vbNewLine
    
End Sub

And there we have it. You could of course add more conditions in there – say, nest another IF statement in the middle that filters out say only red points. It doesn’t have to be points either – this process will work with any properties of any AutoCAD® entity.

Have fun, and if you haven’t already, please do subscribe below!

Will

Excel and AutoCAD® – A match made in heaven – Part 3

One of my readers has requested some help regarding the drawing of windows. I’m not 100% sure of his exact requirements, but it’s a good opportunity for me to build on what I’ve already shown you. This will be a brief post – the main content of which is some annotated VB code (below). This simply takes the previous Excel and AutoCAD® post a little further by creating a practical implementation.

What this does is allows the user to enter an X,Y,Width and Height coordinate in Excel, in columns A,B,C and D respectively. Running the code draws them as rectangles in modelspace.

I’ve decided to compile this as an Excel file, which is downloadable below. It is worth mentioning that I have referenced a specific version of AutoCAD® in this file (Under Tools>References from the VBA editor accessible from Excel using Alt+F11). The code should work if you reference your own version of AutoCAD.

WindowMaker

The source code is here also for you to look at, or paste into your own project. Again, you’ll need to create a reference to AutoCAD® as explained in the previous article.

Sub Main()
Dim ACAD As AcadApplication 'Create ACAD variable of type AcadApplication
On Error Resume Next 'This tells VBA to ignore errors
Set ACAD = GetObject(, "AutoCAD.Application") 'Get a running instance of the class AutoCAD.Application
On Error GoTo 0 'This tells VBA to go back to NOT ignoring errors
If ACAD Is Nothing Then 'Check to see if the above worked
Set ACAD = New AcadApplication 'Set the ACAD variable to equal a new instance of AutoCAD
ACAD.Visible = True 'Once loaded, set AutoCAD® to be visible
End If
ACAD.ActiveDocument.Utility.Prompt "Hello from Excel!" 'Print a message to the AutoCAD® command line
Dim Coords(7) As Double 'This is an array of double precision floating point numbers

Dim n As Integer 'Create the variable n as the type Integer
For n = 1 To 10 'Loop this code, incrementing the value of n from 1 to 10

'Variables for X,Y,Width and Height
Dim X As Double
Dim Y As Double
Dim Width As Double
Dim Height As Double

'Store the values from Excel in memory - We could just use Sheet1.Cells(), but
'this makes it much more readable for us as programmers, and it is faster to retrieve
'data from variables than from the worksheet.
X = Sheet1.Cells(n, 1)
Y = Sheet1.Cells(n, 2)
Width = Sheet1.Cells(n, 3)
Height = Sheet1.Cells(n, 4)

'Lightweight polylines are defined by a series of 2D coords in an array

'Bottom Left Corner
Coords(0) = X
Coords(1) = Y

'Bottom right Corner
Coords(2) = X + Width
Coords(3) = Y

'Top Right Corner
Coords(4) = X + Width
Coords(5) = Y + Height

'Top Left Corner
Coords(6) = X
Coords(7) = Y + Height

'Create a polyline-type variable - its initial value will be empty (non-existent)
Dim PL As AcadLWPolyline

'Create a polyline based on these coordinates
'The Set statement is used when creating a reference to objects (complex data types)
'The value of PL will be a reference to our new polyline
Set PL = ACAD.ActiveDocument.ModelSpace.AddLightWeightPolyline(Coords)   'Add a point in AutoCAD® at this location

'Make PL a closed polyline
PL.Closed = True

Next
End Sub

Hope this helps! And if you want to reap the benefit of spectacular hints and tips like this regularly, subscribe below! Not to mention my outstandingly witty writing style…errmm…so yes please do subscribe!
Will

Join The Dots – How To Create VB.NET AutoCAD® Tools

Been struggling to find time to post on here recently due to exams, work and planning a wedding… with a bit of a lull in between everything, now’s a good time to get back in the swing of it!

Join The Dots – what on earth am I talking about?! Its the name I’ve given to a script I’ve been planning on writing for a while. It is a relatively simple idea, so I am going to take the opportunity to do a step-by-step walkthrough for writing a new AutoCAD® tool.

Preliminary Design

The first thing, is to have an idea. This sounds obvious, but having a clear idea is very important. Writing code will produce specific and predictable results, so you need to know exactly what you want to achieve.

Join The Dots is going to be a tool that will take a series of points, and will draw a polyline through every point, representing the shortest path (hopefully!).

The first step is to think about the idea. I usually ask myself the question “how would you achieve this manually?”

If you didn’t join up the dots in a specific way, you’d no doubt end up with situations like this:

Longest Path
Longest Path

This is in fact the longest path. After thinking about it a bit, I thought that you could start off by finding the shortest distance between any two points. This could form a starting segment for our polyline, and then we could append vertices to the line based on their proximity to the start and end points.

Basically, find the shortest line:

Shortest Path Step 1
Shortest Path Step 1

Then, look for the next closest point to one of the endpoints of the line:

Shortest Path Step 2
Shortest Path Step 2

Repeat:

Shortest Path Step 3
Shortest Path Step 3

And we’ve found the shortest path. So, we have a plan for how to create the tool – time to do some techie stuff!

Setting Up The Project

In my experience thus far, VBA was a lot simpler for interacting directly with AutoCAD®. With .NET, we need to interact with the document database, which is a tad more complex, and involves a bit more code to achieve the same thing.

Ok, firstly we need to set up our project. I’ve already created a template for AutoCAD® applications, so I’ve selected that. Using .NET you will need to set up your project correctly – if you want an easy way to set things up, read through this very short tutorial on setting up a VB.NET template that works with AutoCAD.

Coding The Design

Step 1 – Create a selection

Once we’ve set up the project, the first step in our application is to make a selection that we can use later. We want the user to be able to select the points that are to be used in the JoinTheDots command. The code below goes in your main subroutine – if you’re not sure how things glue together, don’t worry – the full code will be given at the end.

'Setup
Dim ed As Editor = Application.DocumentManager.MdiActiveDocument.Editor

'Declare our filter entries this way
Dim values() As TypedValue = {
New TypedValue(DxfCode.Start, "POINT")
}

'Create the filter using our values
Dim sfilter As New SelectionFilter(values)

'Set the selection options
Dim SelOpts As New PromptSelectionOptions()
SelOpts.MessageForAdding = "Select points to find the shortest path through"
SelOpts.AllowDuplicates = True

'Make the selection
Dim res As PromptSelectionResult = ed.GetSelection(SelOpts, sfilter)

'If the user did something other than make a selection (like pressing ESC), then abort
If Not res.Status = PromptStatus.OK Then Return

'Create a selection set based on the user's selection
Dim ss As Autodesk.AutoCAD.EditorInput.SelectionSet = res.Value

'Create an array of object IDs from the selection set, so that we can loop through them easily
Dim idarray As ObjectId() = ss.GetObjectIds()

So firstly we’re creating the variable ed, which refers to an object known as the Editor. This has many useful functions that we will use later.

Next, we’re going to create a filter that we will use to limit what the user can select. After all, we’re trying to “Join The Dots”, so we only want the user to be able to select AutoCAD® POINTs.

The next step is to ask the user to make a selection – however in order to do that we need to set up some things first. We need to set some PromptSelectionOptions, such as the message we want to display to the user. After these settings have been initialised, we can use the SelOpts variable which contains these settings, and the sfilter variable which contains our filter, to invoke the GetSelection method of our Editor object. This will give the user the opportunity to select any objects on screen, and the result of the user’s actions will be stored in the res variable, which contains a PromptSelectionResult object.

We need to be mindful to any possible action that the user might take. For example, the user might press the Escape key. If this happens, we need to know, and do something else. This is where capturing the result in the res variable is useful. We’re able to tell using res.Status, what type of input the user gave us. If res.Status is “OK”, then we know that the user made a selection as expected. If anything else happened (in other words, if the status is NOT “OK”), then the user did not make a selection as required, and we should exit the subroutine through the Return statement.

Finally, if the user made a selection, we want to convert that into a format that is more useful to us – a selection set. Then, from the selection set, we will retrieve the ObjectIds of the objects that were selected. That way, we are able to retrieve and use the individual entities that were selected at some point later on in our code.

Step 2 – Using Transactions

Developing for AutoCAD® using .NET requires that we wrap any modifications to the document in transactions. To make changes to anything in the drawing we need to start a transaction, make changes to the drawing, add these changes to the transaction, commit the transaction, and then dispose of the transaction. This is perhaps a bit long winded, but is actually inherently has some neat perks. One example is that the AutoCAD® UNDO command will undo the whole transaction rather than individual changes. Another example, is that if there is an error at some point in your code (heaven forbid!!) the whole transaction will be voided, and you’re not left with a drawing that has been half messed with, and half the same as before.

But anyway, you need to use a transaction – and here’s how it’s done:

'Create a link to the active document's database
Dim db As Database = Application.DocumentManager.MdiActiveDocument.Database
'Create a transaction manager object
Dim tm As Autodesk.AutoCAD.DatabaseServices.TransactionManager = db.TransactionManager
'Start a transaction
Dim myT As Transaction = tm.StartTransaction()

'DO STUFF HERE!

'Commit the transaction
myT.Commit()
'Dispose of our transaction
myT.Dispose()

So the first step is to create the variable db which refers to the database of the active document. Next, we create the variable tm, which refers to db.TransactionManager. Then, its just a case of invoking tm.StartTransaction, and storing the resultant Transaction object in the variable myT. Easy huh?

After “DOING STUFF!”, we simply Commit the transaction, then Dispose of it. Changes should then be visible in your drawing (assuming you did make changes!!).

Step 3 – Make Changes To The Drawing

'Create a coord array, the same size as the idarray
Dim points(idarray.GetUpperBound(0)) As DBPoint
'Populate the coord array with point2d objects representing the location of the DBPoint objects
Dim n As Long
For n = 0 To idarray.GetUpperBound(0)
points(n) = tm.GetObject(idarray(n), OpenMode.ForRead, True)
Next

'Find the shortest line formed by two coordinates, create a polyline representing this line, and
'remove the two coordinates from the coords array
Dim pl As Polyline = GetShortestLine(points)

Dim TimeToExit As Boolean = False
Do
TimeToExit = AppendPoint(pl, points)
Loop Until TimeToExit

'Add the polyline to modelspace
AddToModelSpace(db, myT, pl)

If you were thinking it were as simple as the code above – I’m sorry to disappoint you… This code calls a few other functions, so we’ll be looking at those seperately, but the abstract idea for “making changes to the drawing” is as follows.

  • Firstly, get a list of all the DBPoints. To do this, we’ll loop through all the ids in our idarray, and add each DBPoint object to a new array called points().
  • Now, find the two points that are closest together, and we will use those two points to create the first segment of our polyline. This is what the GetShortestLine function does.
  • Now we want to add the remaining points to the polyline, using the point that is closest to one of the ends of the polyline. We want to repeat this step until there are no points remaining.
  • Finally, we need to add the polyline (in the variable pl) to the transaction.

And that’s the high level process. Specific tasks like getting the shortest line, and appending a point the the polyline, are useful to separate from the main subroutine. This makes it much more readable – we can see the high level process, without getting confused with the detail.

Step 4 – Get the shortest line

This code isn’t as complicated as it looks – honest!

Private Function GetShortestLine(ByRef points() As DBPoint) As Polyline

Dim n As Long
Dim m As Long

'Info that needs to be captured
Dim basePoint As DBPoint
Dim endPoint As DBPoint
Dim shortestDist As Double = 1.0E+300 'A very very big number!

'Loop through every combination of point pairs, and find the pair with the shortest distance
For n = 0 To points.GetUpperBound(0)
For m = n + 1 To points.GetUpperBound(0)

Dim tmpDist As Double
tmpDist = points(n).Position.DistanceTo(points(m).Position)

'If this is the shortest distance so far, update
If tmpDist < shortestDist Then
shortestDist = tmpDist
basePoint = points(n)
endPoint = points(m)
End If

Next
Next

'Add the points forming the shortest distance to a new polyline
Dim pl As Polyline = New Polyline
pl.AddVertexAt(0, basePoint.Position.Convert2d(New Plane), 0, 0, 0)
pl.AddVertexAt(1, endPoint.Position.Convert2d(New Plane), 0, 0, 0)

'remove basePoint and endPoint from the array of points, so that they are not reused
RemovePoint(points, basePoint)
RemovePoint(points, endPoint)

Return pl
End Function

Basically, what this function does is iterates through every point, and measures the distance from that point to every other point. The shortest distance found will be remembered, along with the two points that form the shortest distance. The main mechanism at work here is a combination of two For Next loops. The first For Next loop iterates the value of n between 0 and the upper bound of points array. So if there are 10 points, n will loop between 0 and 9. The second For Next loop iterates the value of m between the value of n + 1, and the upper bound of the points array. So, in the case of n=0, m would loop from 1 to 9. This is so that we only make new comparisons – for example, we wouldn’t want to compare points(0) with points(0), as its the same point. So we start the inner loop at n+1. We want the combination of n and m to always be unique, and cover every combination. This is what the pair of For Next loops achieves.

After the For Next loops have completed, we have found the points that create the shortest distance. What we want to do now, is create a polyline based on these two points. What we do, is create a new polyline object, and simply add the vertices to the polyline, at the correct positions, i.e., at 0 for the basePoint, and 1 for the endPoint. This creates a polyline joining up the two points.

Finally, so that in future we do not use these points any more as they are already within our polyline, we want to remove them from the points() array. Here we are using another function to do that.

Step 5 – Remove Item From An Array in VB.NET

There is no native function for removing an item from an array in VB.NET, so we will have to create a function to do this ourselves. It’s a fairly simple idea:

Remove Item From Array
Remove Item From Array

So, we loop from 0 to the upper bound of the array, and when we get to the item number we want to remove, we simply overwrite it with the next value in the array, and keep overwriting each subsequent item from this point.

Here’s the code:

Private Function RemovePoint(ByRef points() As DBPoint, ByRef remPoint As DBPoint) As Boolean
Dim n As Long
Dim newUpperBound As Long = points.GetUpperBound(0) - 1
Dim pointFound As Boolean
'Iterate through the points array until the removePoint is found, then nudge points down the array
For n = 0 To newUpperBound
If Not pointFound Then
If points(n) Is remPoint Then
pointFound = True
End If
End If
If pointFound Then
points(n) = points(n + 1)
End If
Next
'Set the new size of the array, clipping off the last item of the array
ReDim Preserve points(newUpperBound)

If newUpperBound = -1 Then
Return True
Else
Return False
End If

End Function

The final stage in this code is to simply re-dimension the size of the array, to a length 1 shorter than before, thus clipping out the final value, which is no longer needed as it has been copied to the previous item in the array.

Finally, when the array upper bound has reached -1, there are no more items left in the array. In this situation we will Return a value of TRUE, which we will later use as a trigger to stop looking for more points.

Step 6 – Append points to polyline

The final stage of our changes to the drawing is to append points to our polyline in order of how close they are to the ends of our existing polyline. This works on a similar principle to the other subroutine – looping through the points looking for the nearest point, then adding the point to the polyline. This sub will add a single point to the polyline, and remove that point from the points() array.

Private Function AppendPoint(ByRef pl As Polyline, ByRef points() As DBPoint) As Boolean
Dim startPoint As Point2d = pl.StartPoint.Convert2d(New Plane)
Dim endPoint As Point2d = pl.EndPoint.Convert2d(New Plane)
Dim nearestPoint As String = ""
Dim shortestDist As Double = 1.0E+300 'A very very big number!
Dim remPoint As DBPoint

Dim n As Long
For n = 0 To points.GetUpperBound(0)

Dim targetPoint As Point2d
targetPoint = points(n).Position.Convert2d(New Plane)

Dim tmpDist As Double
tmpDist = startPoint.GetDistanceTo(targetPoint)
If tmpDist < shortestDist Then
shortestDist = tmpDist
nearestPoint = "startpoint"
remPoint = points(n)
End If

tmpDist = endPoint.GetDistanceTo(targetPoint)
If tmpDist < shortestDist Then
shortestDist = tmpDist
nearestPoint = "endpoint"
remPoint = points(n)
End If

Next

Select Case nearestPoint
Case "startpoint"
pl.AddVertexAt(0, remPoint.Position.Convert2d(New Plane), 0, 0, 0)
Case ("endpoint")
pl.AddVertexAt(pl.NumberOfVertices, remPoint.Position.Convert2d(New Plane), 0, 0, 0)
Case Else
Err.Raise(0, , "nearestPoint not set!")
End Select

Return RemovePoint(points, remPoint)

End Function

As noted earlier, RemovePoints will Return a TRUE value when there are no more points left in the array. This TRUE or FALSE signal is again passed back through this function, so that we are able to know if this was the last point added. See the Do – While loop, and the TimeToExit variable, in the code earlier on in this document.

Step 7 – Add the polyline to ModelSpace

The final subroutine is to simply add entities to ModelSpace, ensuring that we correctly add it to the transaction too:

Private Sub AddToModelSpace(ByVal db As Database, ByVal myT As Transaction, ByVal ent As Entity)

'Open the ModelSpace Block Table Record
Dim acBT As BlockTable = db.BlockTableId.GetObject(OpenMode.ForRead)
Dim BTR As BlockTableRecord
BTR = acBT(BlockTableRecord.ModelSpace).GetObject(OpenMode.ForWrite)

'Add the entity to the ModelSpace Block Table Record
BTR.AppendEntity(ent)

'Add the entity to the transaction
myT.AddNewlyCreatedDBObject(ent, True)

End Sub

Compiling The Project

If all has been coded correctly, you should now be able to save, and compile your project to a dll file! You can then use the NETLOAD command to load this dll into AutoCAD®, and invoke the command using whatever command name you specified before your main subroutine.

Join The Dots
Join The Dots

Source Files

I’ve included below a zip file containing all the source files for this project – just in case things went awry, you can hopefully find the problem. The command name I’ve used is JOINTHEDOTS.

jointhedots

Source Code

Also, below I’ve pasted ALL the source code for the project:

Imports Autodesk.AutoCAD.DatabaseServices
Imports Autodesk.AutoCAD.Runtime
Imports Autodesk.AutoCAD.Geometry
Imports Autodesk.AutoCAD.ApplicationServices
Imports Autodesk.AutoCAD.EditorInput
Imports Autodesk.AutoCAD.Colors

Public Class Class1

<CommandMethod("JoinTheDots")> _
Public Sub JoinTheDots()
'Setup
Dim ed As Editor = Application.DocumentManager.MdiActiveDocument.Editor

'Declare our filter entries this way
Dim values() As TypedValue = {
New TypedValue(DxfCode.Start, "POINT")
}

'Create the filter using our values
Dim sfilter As New SelectionFilter(values)

'Set the selection options
Dim SelOpts As New PromptSelectionOptions()
SelOpts.MessageForAdding = "Select points to find the shortest path through"
SelOpts.AllowDuplicates = True

'Make the selection
Dim res As PromptSelectionResult = ed.GetSelection(SelOpts, sfilter)

'If the user did something other than make a selection (like pressing ESC), then abort
If Not res.Status = PromptStatus.OK Then Return

'Create a selection set based on the user's selection
Dim ss As Autodesk.AutoCAD.EditorInput.SelectionSet = res.Value

'Create an array of object IDs from the selection set, so that we can loop through them easily
Dim idarray As ObjectId() = ss.GetObjectIds()

'Create a link to the active document's database
Dim db As Database = Application.DocumentManager.MdiActiveDocument.Database
'Create a transaction manager object
Dim tm As Autodesk.AutoCAD.DatabaseServices.TransactionManager = db.TransactionManager
'Start a transaction
Dim myT As Transaction = tm.StartTransaction()

'Create a coord array, the same size as the idarray
Dim points(idarray.GetUpperBound(0)) As DBPoint
'Populate the coord array with point2d objects representing the location of the DBPoint objects
Dim n As Long
For n = 0 To idarray.GetUpperBound(0)
points(n) = tm.GetObject(idarray(n), OpenMode.ForRead, True)
Next

'Find the shortest line formed by two coordinates, create a polyline representing this line, and
'remove the two coordinates from the coords array
Dim pl As Polyline = GetShortestLine(points)

Dim TimeToExit As Boolean = False
Do
TimeToExit = AppendPoint(pl, points)
Loop Until TimeToExit

'Add the polyline to modelspace
AddToModelSpace(db, myT, pl)

'Commit the transaction
myT.Commit()
'Dispose of our transaction
myT.Dispose()

End Sub

Private Function AppendPoint(ByRef pl As Polyline, ByRef points() As DBPoint) As Boolean
Dim startPoint As Point2d = pl.StartPoint.Convert2d(New Plane)
Dim endPoint As Point2d = pl.EndPoint.Convert2d(New Plane)
Dim nearestPoint As String = ""
Dim shortestDist As Double = 1.0E+300 'A very very big number!
Dim remPoint As DBPoint

Dim n As Long
For n = 0 To points.GetUpperBound(0)

Dim targetPoint As Point2d
targetPoint = points(n).Position.Convert2d(New Plane)

Dim tmpDist As Double
tmpDist = startPoint.GetDistanceTo(targetPoint)
If tmpDist < shortestDist Then
shortestDist = tmpDist
nearestPoint = "startpoint"
remPoint = points(n)
End If

tmpDist = endPoint.GetDistanceTo(targetPoint)
If tmpDist < shortestDist Then
shortestDist = tmpDist
nearestPoint = "endpoint"
remPoint = points(n)
End If

Next

Select Case nearestPoint
Case "startpoint"
pl.AddVertexAt(0, remPoint.Position.Convert2d(New Plane), 0, 0, 0)
Case ("endpoint")
pl.AddVertexAt(pl.NumberOfVertices, remPoint.Position.Convert2d(New Plane), 0, 0, 0)
Case Else
Err.Raise(0, , "nearestPoint not set!")
End Select

Return RemovePoint(points, remPoint)

End Function

Private Function GetShortestLine(ByRef points() As DBPoint) As Polyline

Dim n As Long
Dim m As Long

'Info that needs to be captured
Dim basePoint As DBPoint
Dim endPoint As DBPoint
Dim shortestDist As Double = 1.0E+300 'A very very big number!

'Loop through every combination of point pairs, and find the pair with the shortest distance
For n = 0 To points.GetUpperBound(0)
For m = n + 1 To points.GetUpperBound(0)

Dim tmpDist As Double
tmpDist = points(n).Position.DistanceTo(points(m).Position)

'If this is the shortest distance so far, update
If tmpDist < shortestDist Then
shortestDist = tmpDist
basePoint = points(n)
endPoint = points(m)
End If

Next
Next

'Add the points forming the shortest distance to a new polyline
Dim pl As Polyline = New Polyline
pl.AddVertexAt(0, basePoint.Position.Convert2d(New Plane), 0, 0, 0)
pl.AddVertexAt(1, endPoint.Position.Convert2d(New Plane), 0, 0, 0)

'remove basePoint and endPoint from the array of points, so that they are not reused
RemovePoint(points, basePoint)
RemovePoint(points, endPoint)

Return pl
End Function

Private Function RemovePoint(ByRef points() As DBPoint, ByRef remPoint As DBPoint) As Boolean
Dim n As Long
Dim newUpperBound As Long = points.GetUpperBound(0) - 1
Dim pointFound As Boolean
'Iterate through the points array until the removePoint is found, then nudge points down the array
For n = 0 To newUpperBound
If Not pointFound Then
If points(n) Is remPoint Then
pointFound = True
End If
End If
If pointFound Then
points(n) = points(n + 1)
End If
Next
'Set the new size of the array, clipping off the last item of the array
ReDim Preserve points(newUpperBound)

If newUpperBound = -1 Then
Return True
Else
Return False
End If

End Function

Private Sub AddToModelSpace(ByVal db As Database, ByVal myT As Transaction, ByVal ent As Entity)

'Open the ModelSpace Block Table Record
Dim acBT As BlockTable = db.BlockTableId.GetObject(OpenMode.ForRead)
Dim BTR As BlockTableRecord
BTR = acBT(BlockTableRecord.ModelSpace).GetObject(OpenMode.ForWrite)

'Add the entity to the ModelSpace Block Table Record
BTR.AppendEntity(ent)

'Add the entity to the transaction
myT.AddNewlyCreatedDBObject(ent, True)

End Sub

End Class

I hope this tutorial helped you get started with VB.NET in AutoCAD® – you’ll be developing your own tools in no time.

Also, don’t be disheartened if you find it difficult to get things going – its a learning curve, and you will get there. If you think I wrote all this code perfect first time, THINK AGAIN!!! It’s a process of trying things out, experimenting, and persisting.

Please subscribe to my blog if you found this helpful – my primary concern is giving great tips, tricks and tutorials. There is a lot more to come!

Regards,

Will

P.s. Seriously, subscribe!

VB.NET AutoCAD® Template

For those interested in AutoCAD® development, there are a few files that need to be referenced so that you can work with AutoCAD®. So, ideally, you’ll want to set up a basic template that you can use with all the basics set up ready for you. In this tutorial we will do just that.

I’m using visual Basic 2010 Express edition, but the process should be very similar for the latest versions of Visual Basic.

Firstly, create a new project. Select Class Library as your template. This type of file will compile to a .NET dll – just what we need for developing AutoCAD® tools.

Next, go to project properties, and then click on the references tab. There are two files you’ll need to add – AcMgd.dll, and AcDbMgd.dll. You can find these in your AutoCAD® directory – for example:

C:\Program Files\Autodesk\AutoCAD® Civil 3D® 2011

You may also have opted to download the ObjectARX libraries. If so, you can use the files located in here. Using these files allows you to develop for AutoCAD® without having to install AutoCAD® first. Whichever files you reference, ensure that the Copy Local property of both references is set to False. Because the files already ship with AutoCAD®, we do not want to be copying dll files all over the place as this can cause problems.

The next step requires you to save your project first, so do that. Once saved, go back to project properties and click on the Compile tab. Click on Advance Compile Options. Set the Target Framework to the version of the .NET framework that is suitable for the versions of AutoCAD® you are developing for. I have mine set to .NET framework 3.5, as I build my applications for AutoCAD® 2010 and above. Remember that .NET frameworks should be backward compatible, so if in doubt go for earlier versions rather than later versions, unless you have a specific reason not to do so.

We’re nearly there, but there is one last thing I like to do before saving as a template. Go back to your class, and we’re going to put in some default code, with some imports, and a subroutine with an associated command name that we will use when calling the command from the AutoCAD® command line:

Imports Autodesk.AutoCAD.DatabaseServices
Imports Autodesk.AutoCAD.Runtime
Imports Autodesk.AutoCAD.Geometry
Imports Autodesk.AutoCAD.ApplicationServices
Imports Autodesk.AutoCAD.EditorInput
Imports Autodesk.AutoCAD.Colors

Public Class Class1

<CommandMethod("COMMAND_NAME_HERE")> _
Public Sub COMMAND_NAME_HERE()

MsgBox("Hello World!")

End Sub

End Class

And that’s it! The final step is as simple as going File>Export Template, and following the wizard!

Hope this helps,

Will

P.s, sign up below to receive updates on my blog – don’t miss out on any tips like this!

Introduction to VB.NET in AutoCAD

VBA is in the process of being phased out, and the replacement is .NET. This may disappoint some of you that have VBA applications/skills, but you will be pleased to hear that you can still use code that you have, and all you know about VBA programming with AutoCAD® can still be put to good use using VB.NET. The following tutorial sets the foundation for creating your first VB.NET project, and should serve as a good starting point for migrating your code across to VB.NET.

What you need

There is no native IDE (integrated development environment) within AutoCAD® for developing .NET projects like you could with VBA (via the command VBAIDE). Instead, you need an external software development package for writing and compiling your code. Fear not however, you can download express editions of several programming suites for free directly from Microsoft’s website. Click here to visit the page for downloading Visual Basic Express 2010.
Next, you need to download the ObjectARX programming interface from AutoDesk’s website. Click here to visit the page for downloading ObjectARX. Ensure you acquire the correct version of the ObjectARX libraries for your version of AutoCAD.

Create your first VB.NET AutoCAD® project

Open Microsoft Visual Basic Express, and select “New Project”, and then select the “Class Library” option. This creates a project that compiles to a dynamic link library (.dll file). In VBA you automatically have access to the AutoCAD® object, including objects such as ThisDrawing, but in this environment we have to create references to AutoCAD® explicitly ourselves. We do this by naming a reference to ObjectARX, which contains the AutoCAD® type libraries that we want to use.

  1. Under the Solution Explorer window, right click on your project (ClassLibrary1 if you haven’t renamed it), and select Properties.
  2. Click on the References Tab.
  3. Click on the Add dropdown, and select Reference.
  4. Click on the Browse Tab, and navigate to where the ObjectARX libraries are installed. Typically this will be C:\ObjectARX 2011\
  5. Depending on your preference, open the folder inc-win32 or inc-x64.
  6. Select the dll files Autodesk.AutoCAD.Interop.dll and Autodesk.AutoCAD.Interop.Common.dll, and click OK. This imports the AutoCAD® type libraries.
  7. Add another reference, and this time select the inc folder. Select AcMgd.dll and AcDbMgd.dll, and click OK. I’m not completely sure why these are required, but apparently they are!
  8. Finally, you’ll notice on the References Tab that the Copy Local property of the AcMgd.dll and AcDbMgd.dll references is set to True. This needs to be False so select them, and under the properties window change the Copy Local property to False.

Writing some initial code

Our project is now set up with all the references required to link to AutoCAD®. Now we can begin writing code.
Switch back to code view (If you haven’t renamed anything, double click on Class1.vb under Solution Explorer). We need to specify in this class which libraries from our references we intend to use, so right at the top of the class (before Public Class Class1) put the following code:

    'Contains the AutoCAD® Type Library
    Imports Autodesk.AutoCAD.Interop
    'Contains the AutoCAD/ObjectDBX Type Library
    Imports Autodesk.AutoCAD.Interop.Common

Now in VBA, we had access to the ThisDrawing object. This is quite useful, so it would be useful to have the same functionality here. Now that we have access to the AutoCAD® object model, we can add a simple Get procedure to retrieve the object we want. Put the following code inside the Class (between Public Class Class1 and End Class):


    Public ReadOnly Property ThisDrawing As AcadDocument
        Get
            Return Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.AcadDocument
        End Get
    End Property

Now we can use ThisDrawing anywhere within the class as we could in VBA.

Writing our first AutoCAD® Function

Everything is in place now to start producing our own AutoCAD® commands. If you have any VBA code you want to transfer across, you should now be able to paste them into this class. Here’s one that I wrote recently as an example. The code adds a border to MTEXT, and the only difference between this code and VBA code is that I’ve made use of the .NET Try/Catch statement, which is better for error trapping than previous methods in VBA:

    Public Sub CreateMTextBorder()
        Dim ent As AcadEntity
        Dim pnt As Object
        Try
            ThisDrawing.Utility.GetEntity(ent, pnt, "Pick MTEXT to add a border to")
            Dim mt As AcadMText
            mt = ent
            Dim Min As Object, Max As Object
            Dim Coords(7) As Double
            mt.GetBoundingBox(Min, Max)
            Coords(0) = Min(0) - 1
            Coords(1) = Min(1) - 1
            Coords(2) = Max(0) + 1
            Coords(3) = Min(1) - 1
            Coords(4) = Max(0) + 1
            Coords(5) = Max(1) + 1
            Coords(6) = Min(0) - 1
            Coords(7) = Max(1) + 1
            If ThisDrawing.ActiveSpace = AcActiveSpace.acModelSpace Then
                ThisDrawing.ModelSpace.AddLightWeightPolyline(Coords).Closed = True
            Else
                ThisDrawing.PaperSpace.AddLightWeightPolyline(Coords).Closed = True
            End If
        Catch
            ThisDrawing.Utility.Prompt("Invalid Selection")
        End Try
    End Sub

Make sure that your subroutine is declared Public as opposed to Private, so that it will be visible by AutoCAD®. Also, there is one final piece of code we need to add to make this visible by AutoCAD®. We need to add some META data before the subroutine, which tells AutoCAD® that the subroutine is callable directly from AutoCAD®. This is where we assign our subroutine a Command Name for our AutoCAD® Command Line. Add the following code on the line before Public Sub CreateMTextBorder():

	<Autodesk.AutoCAD.Runtime.CommandMethod("MTEXTB")> _

Whatever we put in the brackets will be the command that we type into the AutoCAD® command line.

Our class is now complete, and is ready to be compiled into a DLL file for use with AutoCAD.

Compiling

Under the debug menu, we can select Build to create our .dll, but if we do it now the NETLOAD command in AutoCAD® will fail to load the .dll file. This is because the .dll file must be built on the same version of the .NET framework as the version of AutoCAD® you are using. AutoCAD® 2011 is built on version 3.5 of the .NET framework, so we need to tell VB to compile our class using this version.

  1. Go to project properties as we did before for adding references.
  2. Click on the Compile Tab.
  3. At the bottom, click on Advanced Compile Options.
  4. At the bottom of the Advance Compiler Settings dialog box, select the Target Framework .NET Framework 3.5.
  5. Click OK. This usually requires closing and reopening the project afterwards.

Now we can build our .dll file based on the same .NET Framework as AutoCAD®, which should make it compatable. It is useful to have the Output Window open, so that we can grab the location of the compiled .dll file once the build is complete. You can open this window by going Debug > Windows > Output. Go Debug > Build, and this will compile the .dll file. Copy the path of the .dll file from the Output window, and open AutoCAD.

Type NETLOAD into the command line. This is the command for loading .NET projects. You will be prompted to supply a path, so paste in the path to the .dll file we just created. If it worked, it should silently finish the command, i.e., you shouldn’t get any error messages. Now that the .dll is loaded you should be able to type in the command specified in the META data to call the subroutine inside the .dll, in the case if this example, MTEXTB.


I would like to take this opporunity to suggest that you subscribe to my blog – if you work with AutoCAD® I have many tips and tricks on the tip of my tongue. I guaranteee that you will find it a valuable resource.

VBA in AutoCAD® – Tutorial 2: Subroutines

Introduction

This tutorial assumes that you have read all previous tutorials, but have no other VBA knowledge for AutoCAD® or otherwise.

In this tutorial, I will explain the basics of subroutines.

Subroutines

In the last tutorial, I explained some basic code. The observant of you will have noticed that there were a few parts of the code that I left unexplained:


Sub QuickExample()
End Sub

This is a subroutine. The first line defines the beginning, and the second line defines the end. Similarly to variables (remember those from the last tutorial?), the name can be changed to whatever we like (certain rules apply). However, the subroutine name must end with a pair of parantheses. If they are omitted, they will be added automatically. You may notice after typing a line such as Sub QuickExample and pressing enter, that the End Sub statement is automatically inserted. If any subroutine has no corresponding End Sub, it will not run, and an error will occur.

A subroutine is a container for our code. If we had a subroutine that contains code that converts TEXT to MTEXT, it would be logical to call it something like ConvertText2MText. When we use the command VBARUN from AutoCAD®, we are presented with a dialog box that gives us the option to execute whatever subroutine we like. So in this example, we would pick the option that refers to the ConvertText2MText subroutine.

The above method using VBARUN is how to execute a subroutine directly from AutoCAD®. We can also run the subroutine from within VBA – perhaps from another subroutine. The way to do that, is to write the subroutine name in our code. So to run the subroutine in the example above, we would simply write:

ConvertText2MText

When VB reaches this line of code, it will jump to the line of code that says Sub ConvertText2Mtext(). Then, it will run the content, and when it reaches the End Sub statement, VB will return to where it was originally, and continue to process code as normal. This can be a useful way of splitting up our VBA applications into logical and understandable chunks that can be called upon whenever necessary.

So lets expand on our example. Lets say that we have 4 subroutines. I’ll put 3 of them below:


Sub ConvertText2MText()
    MsgBox "Convert Text to MText"
End Sub

Sub ConvertLines2Polylines()
    MsgBox "Convert lines to polylines"
End Sub

Sub SetupSystemVariables()
    MsgBox "Do setting up of system variables"
End Sub

I’ve omitted the actual code for these subroutines as it isn’t what I’m focusing on. Each of the above could be called directly from AutoCAD® by entering VBARUN into the command line, and then selecting the desired subroutine to run. But if we actually want to run all of them however, it might be useful to set up another subroutine to call them all:


Sub ReformatDrawing()
    MsgBox "Beginning running all subroutines..."
    ConvertText2MText
    ConvertLines2Polylines
    SetupSystemVariables
    MsgBox "Completed!"
End Sub

When you execute the ReformatDrawing subroutine, it will run the other 3 subroutines in the order that they appear. Some of you might say “You could just put all of the code into one subroutine – why not just do that?”. Well yes you could, but when you start making complicated code its not advisable because it makes it hard to follow. Also, programming in this way makes your code nicely reusable because it easy to transfer whole subroutines from one project to another.

More on Subroutines

We’ve covered the purpose, and basic usage of subroutines. Now I will show you what else subroutines can be useful for.

It is sometimes useful for the subroutine to do certain things based on given input. Say for example we wanted a subroutine that inserts some text into the drawing at a changable position. We can make it so that we can give the subroutine some parameters, and make it do things based on the information we provide. In this example, we could make it so that the subroutine will put text into our drawing based on coordinates that we supply. So I will explain how to pass parameters to a subroutine. Here we have a subroutine that inserts text into our drawing (it’s the same as the one in Tutoral 1):


Sub InsertText()

    Dim MyString As String 'Create string variable
    MyString = "Hello! This is the contents of the string variable called MyString" 'set it to something
    MsgBox MyString 'show it in a messagebox

    Dim Point(2) As Double 'This is how to create an array
    Point(0) = 10 'This is x
    Point(1) = 20 'This is y
    Point(2) = 0 'This is z

    Dim TextHeight as double 'Create double precision floating point number variable
    TextHeight = 10 'set textheight to 10

    ThisDrawing.ModelSpace.AddText MyString, Point, TextHeight  'Add text to drawing in modelspace

End Sub

This subroutine is fine, but it’s not very useful because it does the same thing every time! However, if we alter the code slightly, we can make it a bit more intuative. We can alter the subroutine so that it is anticipating additional information. If we do this, we will have to provide the additional information when we call it. To make the subroutine expect additional information, we add contents to the parantheses as follows:


Sub InsertText(MyString as String)

    MsgBox MyString 'show whatever was passed to the subroutine in a messagebox

    Dim Point(2) As Double 'This is how to create an array
    Point(0) = 10 'This is x
    Point(1) = 20 'This is y
    Point(2) = 0 'This is z

    Dim TextHeight as double 'Create double precision floating point number variable
    TextHeight = 10 'set textheight to 10

    ThisDrawing.ModelSpace.AddText MyString, Point, TextHeight  'Add text to drawing in modelspace

End Sub

Now, to call this subroutine we would use something like:
InsertText “Hello! This is some text that I am passing to the subroutine”

This would need to be contained in another subroutine. I’ll rewrite it all so that it makes some sense:


Sub MainSubroutine
    InsertText "Hello! This is some text that I am passing to the subroutine"
End Sub

Sub InsertText(MyString as String)

    MsgBox MyString 'show whatever was passed to the subroutine in a messagebox

    Dim Point(2) As Double 'This is how to create an array
    Point(0) = 10 'This is x
    Point(1) = 20 'This is y
    Point(2) = 0 'This is z

    Dim TextHeight as double 'Create double precision floating point number variable
    TextHeight = 10 'set textheight to 10

    ThisDrawing.ModelSpace.AddText MyString, Point, TextHeight  'Add text to drawing in modelspace

End Sub

If we input VBARUN into the command line, and select MainSubroutine, we should get exactly the same scenario as we did to begin with. However, it happens differently. This time, we are running the subroutine called MainSubroutine, this is in turn calling the subroutine InsertText, and is providing that subroutine with some text. When VB jumps to the InsertText subroutine, it creates a variable called MyString, and automatically stores the text in here. Notice that I have removed some of the code inside the new sub – it no longer needs to create the MyString variable (with the Dim statement), or set the text in that variable, because it now happens automatically as part of the call to the subroutine.

This is a simple example using one parameter. At the moment this doesn’t really add any more functionality, but we can add more parameters as follows:


Sub MainSubroutine
    InsertText "This is the first call to the InsertText Sub", 10, 20, 0, 10
    InsertText "This is call number 2!", 10, 40, 0, 10
    InsertText "This is the third and final call", 10, 60, 0, 10
End Sub

Sub InsertText(MyString as String, XCoordinate as Double, YCoordinate as Double, ZCoordinate as Double, TextHeight as Double)

    Dim Point(2) As Double 'This is how to create an array
    Point(0) = XCoordinate 'Set the contents of Point(0) to whatever is stored in the variable XCoordinate
    Point(1) = YCoordinate 'Set the contents of Point(1) to whatever is stored in the variable YCoordinate
    Point(2) = ZCoordinate 'Set the contents of Point(2) to whatever is stored in the variable ZCoordinate

    ThisDrawing.ModelSpace.AddText MyString, Point, TextHeight  'Add text to drawing in modelspace

End Sub

Now we’ve set up the InsertText subroutine so that it accepts parameters for the contents of the text, the location, and height. This is more useful from a programming point of view because we can now insert some text into the drawing at any position, any height, and containing whatever contents we like; all only using one line of code as shown in the MainSubroutine.


If you found this useful, please do subscribe to my blog – I’ll always be adding something useful!

VBA in AutoCAD® – Tutorial 1: Introduction

Introduction

Knowing a little bit of programming can be extremely useful in the AutoCAD® environment. Before you cringe with dread at the prospect of having to learn a programming language, it sounds far more difficult than it really is. Especially since VB stands for “Visual Basic” – there’s a clue in the name… Writing simple applications for automating tasks isn’t hard – really.

This tutorial assumes no previous knowledge of VBA in AutoCAD® or otherwise. I will explain the very basics of using VBA in AutoCAD®. I will give a brief overview of the VBA IDE (Integrated Development Environment) and I will explain a small snippet of code and how it works.

What is VBA?

If I assume you know nothing about VBA, it follows that you probably don’t know what VBA stands for. VBA stands for Visual Basic for Applications. Most people reading this will probably have heard of Visual Basic, and are aware that it is a Microsoft Windows based programming language. The “for Applications” bit means that it is integrated behind the scenes in the software application you’re using, be it Word, Excel, or in our case AutoCAD.

Step One – Familiarise with the IDE

In order to begin, we need to access the VBA IDE. This is where we will be working. Input VBAIDE into the command line. This opens up a new window which is the VBA IDE. You should have something that looks like the below:

The section of the screen with the big red “1” is the project window. This shows you all the sections of your VBA project. Right click in this area and select insert/module. This will (surprisingly!) insert a module. A module is basically a container for your code. If you like, you can rename the module from “Module1” to something more descriptive of what the module does or is for.

The section of the screen represented by the number 2 is the properties window. Various aspects of your project have certain properties that can be edited. I won’t go into this right now.

The section of the screen represented by the number 3 is the editing window. If it’s grey at the moment (like in the example), you’re not editing anything. If you’ve added a module, chances are you’re looking at a white screen, and you can type stuff. When you add a module, it automatically opens it up in the editing portion of the screen.

The section of the screen represented by the number 4 is a toolbar – in particular the buttons that handle the execution of code. The run button causes VBA to start executing code from the current subroutine (i.e. the subroutine in the module you’re editing that contains the cursor). The pause button will pause execution, and take you to the debugging screen. The current position that VBA has got to will be highlighted with yellow. The stop button halts execution.

Step Two – Add some code, and run it

Make sure you’re at a state where you are editing a module. You might have at the top of the screen the words “Option Explicit”. This is fine, but also don’t worry if it’s not there. You don’t really need to know about this right now. Paste the following code into the module:

Sub QuickExample()
    Dim MyString As String 'Create string variable
    MyString = "Hello! This is the contents of the string variable called MyString" 'set it to something
    MsgBox MyString 'show it in a messagebox
    Dim Point(2) As Double 'This is how to create an array
    Point(0) = 10 'This is x
    Point(1) = 20 'This is y
    Point(2) = 0 'This is z
    Dim TextHeight as double 'Create double precision floating point number variable
    TextHeight = 10 'set textheight to 10
    ThisDrawing.ModelSpace.AddText MyString, Point, TextHeight 'Add text to drawing in modelspace
End Sub

Everything should have pasted in nicely. The text should automatically change into a lovely assortment of black, dark blue, and green colours. If you get any red, then something went wrong when you copy and pasted. When editing code, different colours of text represent different things. Green text represents a comment or note – this text can be anything and is for the purpose of you making your own notes. Dark blue text represents key words that VB understands. Red text indicates anything that contains errors, or the structure of what you’ve written is wrong. However, just because it’s not red, doesn’t necessarily mean that it contains no errors. Black is anything that doesn’t fall under any of the above.

You probably guessed that to execute the code, you click the run button in the toolbar at the top. The shortcut for this button is the F5 key. As an additional point, it is sometimes useful to know how to execute your code without opening the IDE. To do this, you would type in -VBARUN into the command line, followed by you’re subroutine name. This command can conveniently be stored in a toolbar or pallette using the normal process for editing the user interface – CUI. Notice the preceeding “-” in front of the VBARUN command. You probably already know this is an AutoCAD® command modifier. The dash explicitly tells AutoCAD® to expect command line entry. If the dash is omitted, VBARUN opens a little dialog box, and you can pick your subroutine from there.

Step Three – Understanding the code

Maybe you executed the code and thought “that’s not very interesting..”. If so, I assure you this is the tip of a very large iceberg. In the code above, I’ve added comments in such a way that it explains it a little, but I’ll give a little more detail now.

Code is executed from the top down in the same manner you would read a book, unless you tell VB to do something else.

Dim MyString As String

This is the first line of code. This line creates what is known as a variable. The Keyword “Dim” is the part of the code that tells VB to create a variable. A variable is basically a memory bank with a name. The name of the “memory bank” or variable is MyString. We specified the name MyString, but it could have been called something else if we liked. The line Dim This_is_a_variable as string would be equally valid, but it’s a bit of a mouthful. Also, any reference to MyString in the code would need to be changed to This_is_a_variable in order for the code to work in the same way. The part of the code that says “As String” tells VB what type of variable it is. In this case, it is what is known as a String variable, or a Text-String. This basically means that anything that is attempted to be stored into the variable MyString should be interepretted as Text. If it is not text, VB will try to convert it to text. If it cannot, an error will occur with the message “Type Mismatch”

MyString = "Hello! This is text in a string variable!"

This line of code shows you how to set the contents of a variable. It is simply a case of stating that Variable = SOMETHING. The text after the equals sign must be enclosed inside quotation marks. If it was not enclosed in quotation marks, VB would try to execute the text as code, and an error would occur (the text would also be red). So enclosing it in quotation marks tells VB not to interpret this as code, but use it as a text value.

MsgBox MyString

This line of code causes a messagebox to be displayed. The command for doing so is MsgBox. The part following MsgBox in this instance is our variable called MyString, however we didn’t have to use a variable here. We could have explicitly put in any text we like enclosed in quotation marks, and it would be executed in the same way, displaying the inside of the quotation marks instead of the contents of the variable.

Dim Point(2) As Double

This should look quite familiar – again we’re declaring a variable. You’ll notice that it is a different type. In this instance it is a number. Specifically, a double precision floating point number, but we don’t need to know the details yet. You might also have noticed the brackets containing the number 2. The name of the variable is still just Point but this time it is an array. Think of an array as a list within that variable. This particular array has 3 elements to that list – 0, 1, and 2. We’ll probably talk more about arrays at another time.

Point(0) = 10 'This is x
Point(1) = 20 'This is y
Point(2) = 0 'This is z

Similarly to before, this is assigning values to the variable. However this time, as we’re dealing with an array, we need to specify which part of the list we want to assign our value to. Realise that all of the values are actually stored in the same variable, but just organised into a list.

Dim TextHeight As Double
TextHeight = 10

No problems here.

ThisDrawing.ModelSpace.AddText MyString, Point, TextHeight

The final line of code in this subroutine. The first part is quite self explanatory – Thisdrawing.Modelspace.Addtext. This adds some text into the modelspace of the active drawing. If there is no active drawing (i.e, they’re all closed) an error will occur. In order for AutoCAD® to be able to add text, it needs to know certain things: What text do you want to display? Where do you want to display it? What size do you want it to be? At this stage I will point out that the text we are adding is NOT MText. It is Text – what you would get by using the Text command in AutoCAD®. Conveniently, we’ve already set up some variables that contain the values we need. Pop them after Thisdrawing.Modelspace.Addtext in the right order, delimited with a comma, and you’ve now got some text in the modelspace of the current drawing.


I hope you found this a useful introduction – we haven’t don’t anything particularly useful yet, but there’s much much more great info where this came from. If you liked what you read I would encourage a subscription – you won’t regret it!