25 July 2008

Plot Preview Gotcha

Here's a quick fix to a really annoying problem I was having. If you're making a custom plot routine using a modal form and have included plot preview functionality you'll quickly realize that as soon as the plot preview is displayed you're stuck. Completely stuck, escape key, right-click, everything is frozen. Apparently your plot form still has focus, even if hidden.

There's two fixes for this problem:

1) Use a modeless form. This includes adding document locks almost everywhere and is generally a pain in the butt.

2) Add a User Interaction, like this:

  Private Sub PlotPreview()

    Dim edUserInteraction As EditorUserInteraction = Application.DocumentManager.MdiActiveDocument.Editor.StartUserInteraction(Me)





      ' Do your plot preview


    Catch ex As System.Exception

      ' Error handling




      ' Show the form

      If Me.Visible = False Then Me.Show()

    End Try

  End Sub

The EditorUserInteraction object allows your modal form to give up focus and allow the plot preview to do what it's supposed to.

Simple fix for a very frustrating problem.

14 July 2008

Tips on getting started

Bashkim sent me an email from Kosova asking if I had any good advice on how to get started with the managed API. I get this question very often when I'm at events and people find out what I do. Seems there's oodles of people who know AutoCAD inside and out and can program using Lisp of VBA/VB but haven't been able to make the jump to .Net.

The first tip I can give you is: learn to program in VB.Net first. If you already know how to program in VBA/VB6 this should be fairly easy but there are some inherent differences that you'll need to understand before you can really get going. Sites I visit for VB.Net samples and information are The Code Project and the .Net area of Planet Source Code. You can also visit the Visual Basic Developer Center for some great tutorials to get you going.

To get into the managed API, there's not too many places you can go. Kean Walmsley's blog Through the Interface is by far the best resource for insight into the API. He focuses mostly on C# but you can use a code translator to get to VB.Net if needed. There's two news groups I frequent The Swamp (.Net section) and the Autodesk Discussion AutoCAD .Net Group.

07 July 2008

Finding all title blocks on all layouts

I recently came across a pretty common problem where I needed to find all of the title block entities on each layout in the active drawing. The solution lies in the database layout dictionary as can be seen below. Special consideration needs to be taken if you're working with dynamic blocks. If you don't check if the block is dynamic, you're only getting the anonymous block, not the true dynamic block reference.

  <CommandMethod("CollectTblocks")> _

  Public Sub CollectTblocks()

    ' Get the current document database and active drawing

    Dim oDb As Database = HostApplicationServices.WorkingDatabase

    Dim oDwg As Autodesk.AutoCAD.ApplicationServices.Document = Application.DocumentManager.MdiActiveDocument


    ' Start a transaction

    Dim oTrans As Transaction = oDb.TransactionManager.StartTransaction


    ' Get the current layout id and layout name

    Dim curLayoutID As ObjectId = LayoutManager.Current.GetLayoutId(LayoutManager.Current.CurrentLayout)

    Dim curLayout As Layout = CType(curLayoutID.GetObject(OpenMode.ForRead), Layout)


    ' Create an arraylist to store our layout entity ids in

    Dim oTblocks As New ArrayList



      ' Lock the drawing

      Using locked As Autodesk.AutoCAD.ApplicationServices.DocumentLock = oDwg.LockDocument()

        ' Get the drawings layout dictionary

        Dim layoutDict As DBDictionary = CType(oTrans.GetObject(oDb.LayoutDictionaryId, _

                                                                OpenMode.ForRead), DBDictionary)

        Dim indexTb As Integer = 1


        For Each id As DictionaryEntry In layoutDict

          ' Get the layout object

          Dim oLayout As Layout = CType(oTrans.GetObject(CType(id.Value, ObjectId), _

                                                        OpenMode.ForRead), Layout)


          Dim oBtr As BlockTableRecord = CType(oTrans.GetObject(oLayout.BlockTableRecordId, _

                                                                OpenMode.ForRead), BlockTableRecord)

          If oBtr.IsLayout = True Then

            ' Get the enumerator

            Dim oBtre As BlockTableRecordEnumerator = oBtr.GetEnumerator


            ' Loop through all blocks in the block table

            While oBtre.MoveNext

              Dim oEnt As Entity = CType(oTrans.GetObject(oBtre.Current, _

                                                          OpenMode.ForRead), Entity)


              If TypeOf oEnt Is BlockReference Then

                ' Change the entity to a block reference

                Dim oBr As BlockReference = CType(oEnt, BlockReference)


                If oBr.IsDynamicBlock Then

                  ' Get the object id of the dynamic block's parent

                  Dim oDoId As ObjectId = oBr.DynamicBlockTableRecord

                  ' Get the block table record

                  Dim oDBtr As BlockTableRecord = CType(oTrans.GetObject(oDoId, _

                                                                        OpenMode.ForRead), BlockTableRecord)


                  ' Store the block reference id if the name matches

                  If oDBtr.Name.Equals("TITLEBLOCK") Then oTblocks.Add(oBtr.ObjectId)


                  ' Store the block reference id if the name matches

                  If oBr.Name.Equals("TITLEBLOCK") Then oTblocks.Add(oBtr.ObjectId)

                End If

              End If

            End While

          End If


      End Using


      ' Write a message stating how many title blocks were found

      Dim oEd As Editor = oDwg.Editor

      oEd.WriteMessage(oTblocks.Count.ToString & " title blocks were found in the drawing.")


      ' Close the transaction, aborting is faster than committing


    Catch ex As System.Exception

      MsgBox(ex.Message, MsgBoxStyle.Information, "Error in CollectTblocks")


      ' Clean up


    End Try

  End Sub

So what does it do? In a nutshell, the routine above loops through each layout in the active drawing and looks for block references. If it finds a block reference it checks to see if the block is dynamic and then checks to see if the block name matches "TITLEBLOCK". If it finds a match, it stores the block reference object id in the array list.

01 July 2008

Creating a layer

This article builds off of the last as it uses the LayerExists function to see if the layer we want to make already exists.

  Public Function LayerCreate(ByRef oDb As Database, _

                              ByRef oTransMan As DatabaseServices.TransactionManager, _

                              ByVal strName As String) As Boolean

    ' Open a transaction so we can modify the drawing

    Dim oTrans As Transaction = oTransMan.StartTransaction()


    ' Set our return value to nothing

    Dim retVal As Boolean = False



      ' Check to see if the layer layer exists

      If LayerExists(oDb, oTrans, strName) = False Then

        ' The layer doesn't exist so make a new one

        Dim oLayer As LayerTableRecord = New LayerTableRecord


        ' Set the layer's name

        oLayer.Name = strName


        ' Open up the layer table (for writing)

        Dim oLayerTable As LayerTable = CType(oTransMan.GetObject(oDb.LayerTableId, _

                                                                  OpenMode.ForWrite, _

                                                                  False), _



        ' Add the new layer to the layer table



        ' Add the line object to the drawing database

        oTrans.AddNewlyCreatedDBObject(oLayer, True)


        ' Close the open transaction (very important)


      End If


      retVal = True

    Catch ex As Runtime.Exception

      MsgBox(ex.Message, MsgBoxStyle.Information, "Error in LayerCreate")


      ' Clean up the transaction object (very important)


    End Try


    Return retVal

  End Function

Keep in mind that this is a VERY simple layer routine. If this is all you use, all your layers will be white, continuous and have the default width. I'll leave it up to you to add in the extra features. Hint: do it before the oLayerTable.Add(oLayer) line.

Find an existing layer

Before I get to creating new layers, I thought I'd spend a moment on checking if a layer already exists. After all, we can't make a layer if it's already there.

  Public Function LayerExists(ByRef oDb As Database, _

                              ByRef oTrans As Transaction, _

                              ByVal strName As String) As Boolean

    ' Create a layer object

    Dim oLayer As LayerTableRecord


    ' Set our return value to nothing

    Dim retValue As Boolean = False



      ' Open up the layer table (for reading)

      Dim oLayerTable As LayerTable = CType(oTrans.GetObject(oDb.LayerTableId, _

                                                            OpenMode.ForRead, _

                                                            False), _



      ' Loop through each object in the layer table

      For Each objId As ObjectId In oLayerTable

        ' Get the layertablerecord object for the current id

        oLayer = CType(oTrans.GetObject(objId, _

                                        OpenMode.ForWrite), _



        ' Compare the layer name to the strName variable and

        ' make sure we're not checking erased layers

        If oLayer.Name.ToUpper.Equals(strName.ToUpper) And _

          Not oLayer.IsErased Then

          ' Found the layer

          retValue = True

          Exit For

        End If


    Catch ex As System.Exception

      MsgBox(ex.Message, MsgBoxStyle.Exclamation, "Error in LayerExists")

    End Try


    Return retValue

  End Function

Starting with layers (includes finding the current layer name)

Thought I'd add a few related articles today all about layers. I'll post them in separate articles so they don't get all mixed together.

It is a very wise idea to only have one open transaction at any time so most of the functions I post from now on will accept a transaction or a database object, among other things, as arguments.

Finding the current layer's name:

  Public Function GetCurrentLayerName(ByRef oDb As Database, _

                                      ByRef oTrans As Transaction) As String

    ' Create a layer object

    Dim oLayer As DatabaseServices.LayerTableRecord


    ' Set our return value to nothing

    Dim retVal As String = Nothing



      ' Check if the layer exists...

      oLayer = CType(oTrans.GetObject(oDb.Clayer, OpenMode.ForRead), LayerTableRecord)


      ' Return the current layer name

      retVal = oLayer.Name

    Catch ex As System.Exception

      MsgBox(ex.Message, MsgBoxStyle.Information, "Error in GetCurrentLayerName")

    End Try


    Return retVal

  End Function

Passing in the active database object and a transaction object, oDb.Clayer returns the current layer's object id so we need to do a GetObject on it to return the actual layer entity. Once we have it we simply check the name property.

27 June 2008

Adding a line to modelspace

I'll be starting out by posting some basic articles. Easy stuff like adding a line, creating a layer. I'll move onto more complex articles soon.

For today, I'll show you a routine that adds a line to modelspace. Here's the main routine:

  Public Function AddLine(ByVal pt1 As Geometry.Point3d, ByVal pt2 As Geometry.Point3d) As Boolean

    ' Get the active document as an object

    Dim oDWG As Document = Application.DocumentManager.MdiActiveDocument


    ' Get the database in the active drawing

    Dim oDB As Database = oDWG.Database


    ' Open a transaction so we can modify the drawing

    Dim oTrans As DatabaseServices.Transaction = oDB.TransactionManager.StartTransaction


    ' Get the block table for the active drawing

    Dim oBT As BlockTable = CType(oDB.BlockTableId.GetObject(DatabaseServices.OpenMode.ForRead), BlockTable)


    ' Get the modelspace block table record

    Dim oBTR As BlockTableRecord = CType(oBT(BlockTableRecord.ModelSpace).GetObject(OpenMode.ForWrite), BlockTableRecord)


    ' Create the new line object

    Dim oLine As DatabaseServices.Line = Nothing


    ' Set our return value to false

    Dim retVal As Boolean = False



      ' Create the new line given the start and end point passed into the function

      oLine = New DatabaseServices.Line(pt1, pt2)


      ' Add the line to the block table



      ' Add the line object to the drawing database

      oTrans.AddNewlyCreatedDBObject(oLine, True)


      ' Close the open transaction (very important)



      ' Return true if everything worked

      retVal = True

    Catch ex As System.Exception

      MsgBox(ex.Message, MsgBoxStyle.Information, "Error in AddLine")


      ' Clean up the transaction object (very important)


    End Try


    ' Return true if everything worked, false if not

    Return retVal

  End Function

To call this routine, add it to a new vb.net class file. Be sure to reference acdbmgd.dll and acmgd.dll (not copied local) in your project settings and add these five lines at the top of the class file:

Imports Autodesk.AutoCAD

Imports Autodesk.AutoCAD.ApplicationServices

Imports Autodesk.AutoCAD.DatabaseServices

Imports Autodesk.AutoCAD.Geometry

Imports Autodesk.AutoCAD.Runtime

It's good practice to add this to the very top of every file in your project as it will cut down on the number of errors later:

Option Explicit On

Option Strict On

All that's left now is to add a function that will call our AddLine function to create a line in our active drawing.

  <CommandMethod("AddLine")> _

  Public Sub AddLineToModelSpace()


      ' Define our first point at 0,0,0

      Dim oPt1 As New Point3d(0, 0, 0)


      ' Define our second point at 5,5,0

      Dim oPt2 As New Point3d(5, 5, 0)


      ' Call the AddLine function, passing it our two defined points

      If Not AddLine(oPt1, oPt2) Then Throw New System.Exception("Error calling AddLine.")

    Catch ex As System.Exception

      MsgBox(ex.Message, MsgBoxStyle.Information, "Error in WoPrj")

    End Try

  End Sub

If you've copied everything correctly, the program will compile and you can now test out your AddLine function by starting AutoCAD, netloading your DLL and entering AddLine on the command prompt.


All materials on this site are provided "as is" and without any warranty. Any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the authors be liable to any party for any direct, indirect, incidental, special, exemplary, or consequential damages arising in any way out of the use or misuse of this site.