Search code examples
.netvb.netautocad-pluginobjectarx

AutoCAD -INSERT command in .NET


I have been tasked with translating an AutoCAD plugin from VBA to VB.NET, and I'm currently a bit stuck.

The command I'm working on creates a new layer (or select it as the active layer if it already exist), then executes 2 "-INSERT" commands giving a point selected by the user, and a dwg-file. Then, the previous active layer is reset as the active layer.

The insert-command looks something like this:

-INSERT
C:\path\to\file.dwg
<point.x>,<point.y>,<point.z>
<documentScale>

Note: All the line-breaks in the command are added as vbCR (not vbCrLf).

My question is, how can I achieve the same result in .NET against ObjectARX? I can't use SendStringToExecute because it is async (with no callback), so in other words, I can't reset the current layer once it's done executing. There has to be some way to replicate this functionality in pure .NET code, probably using the BlockTable, but I have no idea how.

I've tried following the article found here: http://through-the-interface.typepad.com/through_the_interface/2006/08/import_blocks_f.html, but that had no visible effect on the document at all. I also tried to use myDatabase.Insert(transform, otherDatabase, False) and the command-prompt said something about blocks already existing and thus beeing skipped, yet I still saw no changes. I have no idea how much magic the "-INSERT" command actually does behind the scenes, but would it be viable to replicate it in .NET? Or is it somehow availible to be called as a normal method (not as a text-command sent to be processed by AutoCAD)?


Solution

  • The code example in the Through the Interface post, imports the blocks, but does not insert them into the drawing. You have to create a BlockReference and add it to the model space. It also inserts all the blocks from the file, not the file as a single block.

    Here is the code I use to import a file as a whole block. This function returns a block reference you can insert into your drawing.

        Private Shared Function InsertFile(ByVal FileName as String, ByVal dwgdb As Database, ByVal tr As Transaction) As BlockReference
    
            Dim br As BlockReference
            Dim id As ObjectId
    
            'use a temporary database 
            Using TempDB As New Database(False, True)
    
                'Get block table
                Dim bt As BlockTable = tr.GetObject(dwgdb.BlockTableId, OpenMode.ForWrite, False)
    
                'Create unique block name
                Dim BlockName As String = FileName.Replace("\", "").Replace(":", "").Replace(".", "")
    
                'check if block already exists
                If Not bt.Has(BlockName) Then
                    'check if file exists
                    If IO.File.Exists(FileName) Then
                        'read in the file into the temp database
                        TempDB.ReadDwgFile(FileName, IO.FileShare.Read, True, Nothing)
                        'insert the tempdb into the current drawing db, id is the new block id
                        id = dwgdb.Insert(BlockName, TempDB, True)
                    Else
                        'Throw exception for missing file 
                        Throw New System.Exception(String.Format("File {0} is not found for library item {1}", FileName, item.PartNo))
                    End If
    
                Else
                    id = bt.Item(BlockName)
                End If
    
                'create a new block reference
                br = New BlockReference(New Point3d(0, 0, 0), id)
            End Using
    
            Return br
    
    
        End Function
    

    Here would be the example of using that function to insert a block into the file. In this example I use a jig, which allows the user to drop the object onto a location they want, otherwise you could just set the position.

          ' Get Editor
            Dim ed As Editor = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.Editor
            ' Get Database
            Dim dwg As Database = ed.Document.Database
    
            'Lock document
            Using dl As DocumentLock = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.LockDocument()
    
                '### Changed Try Finally to using, try was hiding errors
                'Begin Transaction
                Using trans As Transaction = dwg.TransactionManager.StartTransaction()
    
                    Dim blockRef As BlockReference = InsertFile(FileName, dwg, trans)
    
                    'check if layer exists/create
                    AcadUtil.AcadFunctions.CheckLayer(LayerName, trans, dwg)
                    blockRef.Layer = LayerName
    
                    'set focus to the editor
                    Autodesk.AutoCAD.Internal.Utils.SetFocusToDwgView()
    
                    'have the user pick insert point
                    Dim BlockMove As New AcadJigs.JigBlockMove(blockRef, False, 0)
                    ed.Drag(BlockMove)
                    'optionally you could just set the .Position of the block reference
    
    
                    ' add it to the current space, first open the current space for write
                    Dim btr As BlockTableRecord = trans.GetObject(dwg.CurrentSpaceId, OpenMode.ForWrite, True, True)
    
                    ' Add block reference to current space
                    btr.AppendEntity(blockRef)
    
                    'Capture the handle
                    handle = blockRef.Handle.Value.ToString
    
                    ' remember to tell the transaction about the new block reference so that the transaction can autoclose it
                    trans.AddNewlyCreatedDBObject(blockRef, True)
    
                    'commit the transaction
                    trans.Commit()
    
                End Using
    
            End Using
    

    And here is also the CheckLayer function I called.

        Public Shared Sub CheckLayer(ByVal Name As String, ByVal tr As Transaction, ByVal dwg As Database)
    
    
            Dim lt As LayerTable = CType(tr.GetObject(dwg.LayerTableId, OpenMode.ForWrite), LayerTable)
    
            If lt.Has(Name) Then
                Return
            Else
                Dim ly As New LayerTableRecord
                ly.Name = Name
                lt.Add(ly)
                tr.AddNewlyCreatedDBObject(ly, True)
            End If
    
        End Sub
    

    Just as a note, Kean's blog is a great resources, I pretty much learned all of the above code from there.

    For completeness sake, here is the Jig class i reference in the insert code,

     Class JigBlockMove
            Inherits EntityJig
    
            Private _CenterPt As Point3d
            Private _ActualPoint As Point3d
            Private _LockZ As Boolean
            Private _Z As Double
    
            Public ReadOnly Property SelectedPoint() As Point3d
                Get
                    Return _ActualPoint
                End Get
            End Property
    
            Public Sub New(ByVal BlockRef As BlockReference, ByVal LockZ As Boolean, ByVal Z As Double)
                MyBase.New(BlockRef)
                _CenterPt = BlockRef.Position
                _LockZ = LockZ
                _Z = Z
            End Sub
    
            Protected Overloads Overrides Function Sampler(ByVal prompts As JigPrompts) As SamplerStatus
                Dim jigOpts As New JigPromptPointOptions()
                jigOpts.UserInputControls = (UserInputControls.Accept3dCoordinates Or UserInputControls.NoZeroResponseAccepted Or UserInputControls.NoNegativeResponseAccepted)
                jigOpts.Message = vbLf & "Enter insert point: "
                Dim dres As PromptPointResult = prompts.AcquirePoint(jigOpts)
                If _ActualPoint = dres.Value Then
                    Return SamplerStatus.NoChange
                Else
                    _ActualPoint = dres.Value
                End If
    
                Return SamplerStatus.OK
            End Function
    
            Protected Overloads Overrides Function Update() As Boolean
                If _LockZ Then
                    _CenterPt = New Point3d(_ActualPoint.X, _ActualPoint.Y, _Z)
                Else
                    _CenterPt = _ActualPoint
                End If
    
                Try
                    DirectCast(Entity, BlockReference).Position = _CenterPt
                Catch generatedExceptionName As System.Exception
                    Return False
                End Try
    
                Return True
            End Function
    
            Public Function GetEntity() As Entity
                Return Entity
            End Function
    
        End Class
    

    One note regarding working in .NET ObjectARX, there is a an issue with the single threaded nature of AutoCAD, and the fact that the .NET garbage collector runs on a separate thread. If you create any temporary AutoCAD objects that do not get added to the database, you must explicity call .Dispose() on them, or AutoCAD may crash! The crash will seem random too, since it will be triggered by the garbage collector thread. See this post, http://through-the-interface.typepad.com/through_the_interface/2008/06/cleaning-up-aft.html.