Speed up Trim and Extend

Hi Everyone,

I’ve had a quick tip forwarded to me for speeding up using the Trim and Extend commands.

To speed up Trim and Extend, edit the commands in the CUI to include a space and a semi colon and add them to the right click default shortcut menu:

^C^C_extend ;

This is quite a handy little tip, and I’m sure there are other useful commands we could add to the right click menu as well.

Many thanks to Neil Jones for sharing this with us.

Will

The XLine Command and Creating Levels in your Drawing

With the Ortho Mode and Polar Tracking at your disposal, you may be tempted to completely ignore the concept of construction lines. But I certainly still use them, as they are actually really useful for specifying a logical concept in your drawing – for instance, a level.

Basic usage of the XLINE command is simple. Enter XLINE at the command line, followed by two points that define an origin and a direction. There are additional options at the command prompt – H and V constrain the construction line to the Horizontal and Vertical directions respectively. A forces the xline to be drawn at a specific angle, which can also sometimes be useful. The O option is basically the same as the offset command, but forces the new line to be an XLINE rather than imitating whatever you selected. The B option I’ve never actually used before, but this option draws the XLINE that bisects, or is the midpoint of the angle you enclose with three points.

I mentioned that XLINEs are useful for levels, but when drawing a section or elevation with known levels, it can sometimes be clumsy inputting the elevations. For this reason, I always draw sections and elevations at their real world level in the Y direction.

 

 

Here for example, we are showing a level of 0, and 4 above some datum. Here it would be good practice to ensure that the level we have shown on the drawing is actually the level in the Y axis of the drawing. That way, we can be more certain that the drawing is correct. It also makes checking very easy using the ID command, by simply looking at whatever it says for the Y axis, and comparing with what we know it should be.

But there still is the problem of inputting the levels in a quick and elegant way. Up until recently, I had been using an XLINE drawn horizontally at a level of 0, and then offsetting upwards the amount required. This works, but it’s not the quickest way. A slight improvement is to invoke the XLINE command, press H for Horizontal, and then input a coordinate manually, using 0 (or any other number) for the X coordinate, and the desired level for the Y coordinate. This works, but still (for me) isn’t as elegant as it could be.

So instead, I decided to create a neat little lisp, for getting an XLINE at a specific level on the drawing:

level.lsp

This simple command asks you for a level, and an XLINE appears in your drawing at that level. It also scales what you enter by a factor of 1000, so that levels displayed in metres are instantly converted to millimetres. I’d suggest having a look at the lisp code, as it is really quite a good example of how a little bit of lisp can get a job done. It also gives you the opportunity to optimise the code for whatever units you are using.

All for now,

Will

 

AutoCAD® .NET Load From Remote Sources

There are various security features included in the .NET framework, and one of which prevents the loading of dll files from networked locations such as the Internet.

Whilst this is a very sensible idea, it can prevent us from achieving functionality that we want. For example, you may want to store your dll files in some networked location so that it is accessible by other members of your team. It is usually far more convenient to keep one central networked copy of a dll file than to have to duplicate it on all the machines that use it.

So, it would be handy if there was a way to override this security restriction, and of course, there is.

In your AutoCAD® directory (mine is C:\Program Files\Autodesk\AutoCAD® Civil 3D® 2012) there is a file called Acad.exe.Config – this is an xml document which sets out many configuration settings. Open this with any text editor (by the way, have you tried Notepad++ ?), and right before the final closing </configuration> tag, insert the following:


<runtime>

<loadFromRemoteSources enabled="true" />

</runtime>

Now once you restart AutoCAD®, you should be able to NETLOAD dll files from networked locations, and the management of your .NET programs is now that little bit easier.

Aren’t you glad you came to HowToAutoCAD.com :D.

Will

P.S. If you want to benefit from my bountiful wisdom indefinitely (heh), I’d recommend subscribing below.

Speed Up AutoCAD

Everybody wants a faster AutoCAD®. The problem is there are so many ways to tweak and optimise the software that it can be a very daunting task. Thankfully, there are countless articles online that list precisely what can be done to help speed up AutoCAD.

Here is a very good list created by Ellen Finkelstein, which covers many of the ways to speed your AutoCAD® display when dealing with resource-hungry work:

Text

A large drawing with lots of text can slow down your work. Here are some text tips.

Quicktext

Quicktext turns text into rectangles and quickens your regens. Use the QTEXT command and choose on (displays rectangles) or off (displays the text). Then use the REGEN command .

Font substitution

Using a simpler font can also speed up your drawing, especially if it’s large and contains a lot of text. Create a new text style that uses one of AutoCAD’s own fonts, rather than TrueType fonts. You can switch back just before plotting. Note that a different font will take up a different amount of space, so the text may not fit properly. Two fonts that are very simple are txt.shx and simplex.shx.

To create a style:

  1. Start the STYLE command.
  2. In the Text Style dialog box, click New.
  3. In the New Text Style dialog box, give the text style a name and click OK.
  4. Back in the Text Style dialog box, choose a font from the Font Name drop-down list.
  5. Change other settings as desired, such as the height, width factor, and oblique angle. You can also make the text style annotative. You’ll see a preview at the lower-left corner of the dialog box.
  6. Click Set Current if you want to use the style right away. Click Close.

An easy way to change all the text in your drawing to your new style is as follows:

  1. Press Ctrl + A to select all objects. If you have Quick Properties on (it’s a button on the status bar), you’ll get a small window showing the properties of your objects.
  2. Click the drop-down list to select Text or MText; this filters out the other objects.
  3. In the Quick Properties window, click the current style and then its down arrow.
  4. Choose a new style from the list.
  5. Press Esc to deselect all the text.

Turn off text layers

You can always turn off text layers. From the Layer drop-down list, click the light bulb symbol next to a layer. Repeat to turn the layer back on.

Solid hatches and lineweights

Solid fills (solid hatches), wide polylines, and 2D solids can take a while to display if you have lots of them. Just use the FILL command and set it to OFF. Use the REGEN command to see the result.

If you’re using lineweights, these are also displayed as solidly filled areas, so you can turn off their display. Just click the Show/Hide Lineweight button on the status bar.

Regens and resolution

AutoCAD® automatically regenerates whenever needed, but you can turn off automatic regeneration and manually regenerate (using the REGEN command) when you want to; this reduces regenerations and gives you more control. Use the REGENAUTO command and set it to off.

The VIEWRES command sets the resolution that controls circles and arcs. You may have seen a circle that looked like a polygon. Usually, you can use the REGEN command to return its circle-ness. But you can speed up display by lowering the view resolution. Acceptable values are 1-20000. You can set it to 15 for example. A value like 2000 will usually suffice to show you smooth circles again.

When you use this command, you’ll be asked if you want fast zooms. You do. This is a legacy setting.

I hope you found this list as useful as I have. Ellen has a great blog full of all sorts of AutoCAD® tips, I suggest you go have a snoop around as there are many useful gems in there.

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!

Ortho Mode vs. Polar Tracking

Ortho mode and polar tracking are two methods of constraining user input to an axis. You’ve probably become accustomed to using one of them, but if you think they are the same, you may want to look again. Of course, I am talking about these options at the bottom of AutoCAD:

Ortho Mode

Ortho mode constrains user input to horizontal and vertical input. That is, if you draw a line, it will draw it from the first point in either the horizontal or vertical direction. Of course if you snap to an object this will override ortho mode, but you can still use other objects as a reference by using object snap tracking. With object snap tracking, hovering over a snap point will cause a small temporary marker to appear, and you can then use the horizontal and vertical axes of that point as a reference. But, I digress… with ortho mode enabled, you are constrained horizontally and vertically, which is often complemented by the use of object tracking.

Polar Tracking

Polar tracking does not force the constraint to either the horizontal or vertical axis in the same way that ortho mode does. With polar tracking, you are only constrained to the axis if you are in close proximity to the axis, in a similar way to how you will only snap to objects you hover over. During a command if you are hovering over the horizontal or vertical axis, polar tracking will constrain you to that axis. In my opinion, this is the better way, because there is very rarely the need to turn this option off. If you want to draw vertically, hover in the vertical direction. If you want to draw horizontally, hover in the horizontal direction. If you want to draw randomly, draw in a random direction! I believe that polar tracking gives the user a bit more freedom to choose, without the need to turn it on/off.

There is another reason I particularly enjoy polar tracking – a technique I’ve called polar tracking 45. And, this is simply setting up your polar tracking to include tracking in 45° increments.

What’s the point of that you say? You rarely draw things with 45° angles so you hardly see this being useful…

Fair point, but what IS useful, is bearing in mind a few properties of lines drawn at 45°. For example, if you draw horizontally from 45° line, whatever distance you draw will be the exact same distance vertically:

This can be handy in situations where you want to maintain some known distance. Also, having a 45° can work wonders in combination with the mirror command – you’ll be inventing new and wonderful ways to draw things all day:

You’d have never guessed that this cross, comprised of only right angles, was actually drawn using 45° angles. This is only an arbitrary example, but there are a surprising amount of practical uses you will find when working. Try it out for a day – you’ll like it!

So that’s my piece on ortho mode vs polar tracking – in my opinion, polar is the way to go.

If you found this post useful, please do subscribe – I’m always trying to add new tips like this. So if you liked this, you should like what’s to come. Also, I’ve decided that it could be of benefit to this site to add the work of others on here, subject to suitability of course – so, if you have written a manual about something CAD related and want to become a published author (lol), get in touch! I’d love to hear what you have to say.

Thanks for your ongoing support.

Will