.NET
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Inserting a Block with Attributes

19 REPLIES 19
SOLVED
Reply
Message 1 of 20
mgorecki
2670 Views, 19 Replies

Inserting a Block with Attributes

Hello, this is the code that I've put together by reading posts on here and elsewhere. 

I have drawings in a library folder like "SMD_0201.dwg".  The drawing consists of a rectangle and an invisible attribute.

I want to bring the appropriate block (dwg) into my drawing and place the references at the correct location and rotation with the correct attribute text.

 

When I use this, all seems to run fine (the rectangles show up placed perfectly and rotated), but there are no attributes.  Then when I double pick on the block, it does not have a block name, it doesn't even have a definition. 

 

BlockName = the path and name of the block

basename = block name

 

Public Sub InsertSMDComp(ByVal InsPt As Point3d, ByVal BlockName As String, ByVal basename As String, ByVal RefDes As String, _
        ByVal rotation As Double, ByVal scale As Double)
        Dim smdTransMan As DatabaseServices.TransactionManager
        Dim smdTrans As DatabaseServices.Transaction
        Dim myDwg As Document
        Dim myBT As BlockTable

        myDwg = Application.DocumentManager.MdiActiveDocument
        smdTransMan = myDwg.TransactionManager
        smdTrans = smdTransMan.StartTransaction
        myBT = myDwg.Database.BlockTableId.GetObject(OpenMode.ForRead)
        Dim myDB As Database
        myDB = myDwg.Database
        Try
            Using db As Database = New Database(False, True)
                db.ReadDwgFile(BlockName, IO.FileShare.Read, True, "")
                Dim BlkId As ObjectId
                BlkId = myDB.Insert(BlockName, db, True)
                Dim bt As BlockTable = smdTrans.GetObject(myDB.BlockTableId, OpenMode.ForRead, True)
                Dim btr As BlockTableRecord = smdTrans.GetObject(bt(BlockTableRecord.ModelSpace), OpenMode.ForWrite, True)
                Dim bref As BlockReference
                bref = New BlockReference(InsPt, BlkId)
                bref.Rotation = rotation
                bref.ScaleFactors = New Scale3d(scale, scale, scale)
                btr.AppendEntity(bref)
                smdTrans.AddNewlyCreatedDBObject(bref, True)
                'Set the Attribute Value
                Dim myAttColl As DatabaseServices.AttributeCollection
                Dim myEnt As DatabaseServices.Entity
                Dim myBTREnum As BlockTableRecordEnumerator
                myAttColl = bref.AttributeCollection
                myBTREnum = btr.GetEnumerator
                While myBTREnum.MoveNext
                    myEnt = myBTREnum.Current.GetObject(OpenMode.ForWrite)
                    If TypeOf myEnt Is DatabaseServices.AttributeDefinition Then
                        Dim myAttDef As DatabaseServices.AttributeDefinition = myEnt
                        Dim myAttRef As New DatabaseServices.AttributeReference
                        myAttRef.SetAttributeFromBlock(myAttDef, bref.BlockTransform)
                        myAttRef.TextString = RefDes
                        myAttColl.AppendAttribute(myAttRef)
                        smdTrans.AddNewlyCreatedDBObject(myAttRef, True)
                    End If
                End While
                smdTrans.Commit()
            End Using
        Catch ex As Autodesk.AutoCAD.Runtime.Exception
            MsgBox(ex.ToString)
        End Try
        smdTrans.Dispose()
        smdTransMan.Dispose()
    End Sub

 

Help!

Thanks,

Mark

19 REPLIES 19
Message 2 of 20
norman.yuan
in reply to: mgorecki

The problem that you get a block definition without name created in the drawing, is because the block definition was inserted into this drawing with an INVALID block name. The offending code is:

 

BlkId = myDB.Insert(BlockName, db, True)

 

necause BlockName is a file name with a full folder path, which includes characters line ":","\" that are not allowed to be used in block name. When you manulally define block, AutoCAD validates the block name; while you inserts block with .NET API's Database.Insert(), if the block name contaims invalid characters, the block definition can still be inserted, the AutoCAD somehow gives the block definition an empty name, instead of raising an exception. I personally think this is an API bug.

 

Based on your sub's signature, I think you meant to use "baseName" as block name in the Insert() method.

 

However, you should follow AutoCAD convention to use block's file name as Block name, in general. So, you do not need to pass "baseName" to the subroutine.

 

you can get block file name from its full path name like this:

 

Dim bName As String=System.IO.Path.GetFileNameWothoutExtension(BlockName)

BlkId=nyDB.Insert(bName,...)

 

 

 

 

Message 3 of 20
mgorecki
in reply to: norman.yuan

Hi Norman,

Thanks, but when I try to use you suggestion, it tells me "GetFileNameWothoutExtension is not a member of 'System.IO.Path"

 

Above the sub, I do have: 

Imports System.IO

 

Also, if I don't include the path, how does it know where to get the drawing?

 

Thanks

Message 4 of 20
mgorecki
in reply to: mgorecki

Never mind, I got it.

Since I already have the "basename" I used that instead.

Try

           

Using db AsDatabase = NewDatabase(False, True)

     db.ReadDwgFile(BlockName, IO.FileShare.Read, True, "")  

  'Dim bName As String = System.IO.Path.GetFileNameWothoutExtension(BlockName)

   Dim BlkId AsObjectId

     BlkId = myDB.Insert(basename, db, True)

Message 5 of 20
mgorecki
in reply to: mgorecki

Ok, was too quick on that.  The blocks come in, and they look good.  They even have names and definitions (including an attribute definition) in the block editor.  The only problem now is how to insert them and apply a text value to the attribute?

I thought this should work.  it includes the value for the attribute textstring.

While myBTREnum.MoveNext
                    myEnt = myBTREnum.Current.GetObject(OpenMode.ForWrite)
                    If TypeOf myEnt Is DatabaseServices.AttributeDefinition Then
                        Dim myAttDef As DatabaseServices.AttributeDefinition = myEnt
                        Dim myAttRef As New DatabaseServices.AttributeReference
                        myAttRef.SetAttributeFromBlock(myAttDef, bref.BlockTransform)
                        myAttRef.TextString = RefDes
                        myAttColl.AppendAttribute(myAttRef)
                        smdTrans.AddNewlyCreatedDBObject(myAttRef, True)
                    End If
                End While

 

Message 6 of 20
mgorecki
in reply to: mgorecki

Also, when I try to do a manual insert on one of these existing blocks, there is a problem.

When I pick a name from the list, and pick ok to insert, it pops up with "The specified file was not found.  Specify the correct filename or select a valid block name."

 

Message 7 of 20
norman.yuan
in reply to: mgorecki

Sorry, there was a typo:

 

GetFileNameWothout...() SHOULD HAVE BEEN GetFileNameWithout...()

Message 8 of 20
norman.yuan
in reply to: mgorecki

If you had stepped through your code in debugging, you would have easily found out thta the code inside the "If...End If"

 

If TypeOf myEnt Is... Then

 

End If

 

is never reached, thus, no attribute reference is added to the BlockReference. Do not just run code and wonder why it is not work. Debugging teaches a lot.

 

The problem is with Enumerator "myBTREnum", which you declare as BlockTableEnumerator. Why you need to iterate through BlockTable for an AttributeDefinition that belongs to a BlockTablerecord? You'll never find an AttributeDefinition in BlockTable.

 

Instead, what you need is to interate through the BlockTableRecord your BlockReference is based on to find AttributeDefinition. The code would be like:

 

''btr is the BlockTableRecord

bref=New Blockreference(....)

....

 

For Each id As ObjectId in btr

    myEnt=id.GetObject(OpenMode.ForRead)

    If TypeOf myEnt Is AttributeDefinition Then

 

        Dim myAttDef As AttributeDefinition = myEnt

 

        ''Create attribute reference based on the attributeDefinition

        Dim attrRef As New Attributereference

        ''''

 

    End If

Next

Message 9 of 20
norman.yuan
in reply to: mgorecki

Debugging, and Debugging. If you step thorugh the code, or place a break point at where InsertSMDComp() is called, you simply examine what the block name and block file path is passed to this subroutine, then you probably can tell why you get that error message.

 

If the code passes a file name without full path, the .NET runtime would only examine the current application folder for the block file. Thta is probably why the message. You need either supply full path, or write code to search through AutoCAd supporting folders to find block file by only given file name.

Message 10 of 20
mgorecki
in reply to: norman.yuan

Hi Norman, you, as always, have been very helpful.  I did run through the code many, many times.  I put in the breaks, even inside the If...End If block and it stopped there.  I had watches going as well.  That's why this was so confusing and frustrating.

The blocks in my library include a rectangle and a block attribute, but sometimes it sounds like I'm having to add a new attribute to the block that just got read in.

I had the code you mentioned above in regards to btr and bref:

 Dim btr As BlockTableRecord = smdTrans.GetObject(bt(BlockTableRecord.ModelSpace), OpenMode.ForWrite, True)
                Dim bref As BlockReference
                bref = New BlockReference(InsPt, BlkId)
                bref.Rotation = rotation
                bref.ScaleFactors = New Scale3d(scale, scale, scale)
                btr.AppendEntity(bref)

 Since bref was included in the If..End If, I thought it was using the attribute in the block. 

When I look up some of these things, like enumerator, most of the help I find is really no help.  All of the writers think their audience must be full time programmers with multiple degrees.  I'm not one to just splice code together and cross my fingers hoping it will work.  I search the Internet trying to understand what it is.

Thanks for all the time you've already spent helping me.  I really do appreciate it.

 

Best regards,

Mark

Message 11 of 20
mgorecki
in reply to: mgorecki

Unfortunately, the blocks being read in, still do not have usable attributes.  Also, I cannot insert any of them manually.

Message 12 of 20
mgorecki
in reply to: norman.yuan

Hi Norman,

Ok, I did some additional code thinking that maybe the blocks keep getting overwritten when the same block needs to get inserted.

I put a break in as you suggested and found it keeps failing in the "Else" section

Dim bref AsNew DatabaseServices.BlockReference(InsPt, myBT(baseName))  <--crashes here (InsPt and basename are valid)

I know you're busy, but could you explain BlockTables and BlockTableReference, as I can't see why this wouldn't work.

Public Sub InsertSMDComp(ByVal InsPt As Point3d, ByVal BlockName As String, ByVal RefDes As String, _
        ByVal rotation As Double, ByVal scale As Double)
        Dim smdTransMan As DatabaseServices.TransactionManager
        Dim smdTrans As DatabaseServices.Transaction
        Dim myDwg As Document
        Dim myBT As BlockTable
        Dim myBTR As BlockTableRecord
        Dim baseName As String = System.IO.Path.GetFileNameWithoutExtension(BlockName)

        myDwg = Application.DocumentManager.MdiActiveDocument
        smdTransMan = myDwg.TransactionManager
        smdTrans = smdTransMan.StartTransaction
        myBT = myDwg.Database.BlockTableId.GetObject(OpenMode.ForRead)
        If myBT.Has(baseName) Then
            myBTR = myBT(BlockTableRecord.ModelSpace).GetObject(OpenMode.ForWrite)
            Dim myBlockRef As New DatabaseServices.BlockReference(InsPt, myBT(baseName))
            myBlockRef.Rotation = rotation
            myBlockRef.ScaleFactors = New Geometry.Scale3d(scale, scale, scale)
            myBTR.AppendEntity(myBlockRef)
            smdTrans.AddNewlyCreatedDBObject(myBlockRef, True)
            'Set the Attribute Value
            Dim myEnt As DatabaseServices.Entity
            For Each id As ObjectId In myBTR
                myEnt = id.GetObject(OpenMode.ForRead)
                If TypeOf myEnt Is AttributeDefinition Then
                    Dim myAttDef As AttributeDefinition = myEnt
                    'Create attribute reference based on the attributeDefinition
                    Dim attrRef As New AttributeReference
                    attrRef.SetAttributeFromBlock(myAttDef, myBlockRef.BlockTransform)
                    attrRef.TextString = RefDes
                    myBlockRef.AttributeCollection.AppendAttribute(attrRef)
                    smdTrans.AddNewlyCreatedDBObject(attrRef, True)
                End If
            Next
            smdTrans.Commit()
        Else
            Try
                Using db As Database = New Database(False, True)
                    Dim myDB As Database = myDwg.Database
                    db.ReadDwgFile(BlockName, IO.FileShare.Read, True, "")
                    'Dim bt As BlockTable = smdTrans.GetObject(myDB.BlockTableId, OpenMode.ForRead, True)
                    Dim btr As BlockTableRecord = smdTrans.GetObject(myBT(BlockTableRecord.ModelSpace), OpenMode.ForWrite, True)
                    Dim bref As New DatabaseServices.BlockReference(InsPt, myBT(baseName))
                    bref.Rotation = rotation
                    bref.ScaleFactors = New Scale3d(scale, scale, scale)
                    btr.AppendEntity(bref)
                    smdTrans.AddNewlyCreatedDBObject(bref, True)
                    'Set the Attribute Value
                    Dim myEnt As DatabaseServices.Entity
                    For Each id As ObjectId In btr
                        myEnt = id.GetObject(OpenMode.ForRead)
                        If TypeOf myEnt Is AttributeDefinition Then
                            Dim myAttDef As AttributeDefinition = myEnt
                            'Create attribute reference based on the attributeDefinition
                            Dim attrRef As New AttributeReference
                            attrRef.SetAttributeFromBlock(myAttDef, bref.BlockTransform)
                            attrRef.TextString = RefDes
                            bref.AttributeCollection.AppendAttribute(attrRef)
                            smdTrans.AddNewlyCreatedDBObject(attrRef, True)
                        End If
                    Next
                    smdTrans.Commit()
                End Using
            Catch ex As Autodesk.AutoCAD.Runtime.Exception
                MsgBox(ex.ToString)
            End Try
        End If

        smdTrans.Dispose()
        smdTransMan.Dispose()
    End Sub

 

 Thank you

Message 13 of 20
norman.yuan
in reply to: mgorecki

Well, the error is quite obvious to me: in the the If myBT.Has(baseName) Then... Else...End If statement, I saw your logic is like:

 

If The block definition in fond in the BlockTable Then

    ''Insert the Block

Else

   ''Create a side Database from the block file;

   ''Insert it into the drawing database->Here You FORGOT TO DO IT!!!

   ''Insert the block

End If

 

Since in the Else clause you did not insert the Block file into the drawing as Block definition (e.g. MyDb.Insert(baseName, db, False)), thus the error.

 

Your code in the Then... branch is wrong too, (had you test it with a drawing that already has the block definition in it, you would have run into an exception at

 

myBlockRef=New BlockReference(intPt, myBT)

 

Because, myBT is a BlockTable, not a BlockTableRecord Id (type of ObjectId). It should be

 

myBlockref=New BlockReference(intPt, myBT(baseName))

 

Also, you might want to lean up your code a bit, because you have duplicated code to create Blockreference in both branches of the IF.. statement. You can do it this way (psuedo-code)

 

//Get BlockTable

BlockTable bt=...

 

//Test if the block definition exists or not

Dim BtrId As ObjectId=ObjectId.Null

If bt.Has(baseName) Then btrId=bt(baseName)

 

If BtrId.IsNull Then

    //Insert block file

    Using dbAs Database=new Database(False,True)

        db.ReadDwgFile(....)

        BtrId = myDb.Insert(baseName, db, False)

    End Using

End If

 

//Now, your are ready to create a Blcokreference

myBlockRef=New Blockreference(insPt, BtrId);

....

 

Message 14 of 20
mgorecki
in reply to: norman.yuan

Ok, the drawings are being read into my drawing as blocks (with attributes).

Here's the part I don't understand, I added this to check for the attribute definition:

myBTR.AppendEntity(bref)

smdTrans.AddNewlyCreatedDBObject(bref,True)

'Set the Attribute Value

If myBTR.HasAttributeDefinitions Then....

 

Even though the drawings (consisting of a polyline and an attribute definition) get read into my drawing as blocks (they have the polyline and att. def. in the block), myBTR.HasAttributeDefinitions shows no attribute definitions.  It skips the code after this if.  The watch also shows no attribute definitions.  How can that be if I can see them?  

Message 15 of 20
norman.yuan
in reply to: mgorecki

In your code, myBTR is ModelSpace block, NOT the block definition that you use to create the BlockReference!

 

You need to open the block definition then loop thorugh its AttributeDefinition. Something, after you created your BlockReference, like:

 

Dim bDef As BlockTableRecord=myTran.GetObject(myBT{baseName), OpenMode.ForRead)

 

If (bDef.HasAttributeDefinitions) Then

    ...

End If

Message 16 of 20
mgorecki
in reply to: norman.yuan

Hi Norman, thank you for being patient with me.  This was quite a learning experience, and I appreciate the time you spent.

Here is the updated code, cleaned up for anyone who needs it.

 

Public Sub InsertSMDComp(ByVal InsPt As Point3d, ByVal BlockName As String, ByVal RefDes As String, _
        ByVal rotation As Double, ByVal scale As Double)
        Dim smdTransMan As DatabaseServices.TransactionManager
        Dim smdTrans As DatabaseServices.Transaction
        Dim myDwg As Document
        Dim myBT As BlockTable
        Dim myBTR As BlockTableRecord
        Dim baseName As String = (System.IO.Path.GetFileNameWithoutExtension(BlockName))

        myDwg = Application.DocumentManager.MdiActiveDocument
        smdTransMan = myDwg.TransactionManager
        smdTrans = smdTransMan.StartTransaction
        myBT = myDwg.Database.BlockTableId.GetObject(OpenMode.ForRead)

        Dim BtrId As ObjectId = ObjectId.Null
        If myBT.Has(baseName) Then BtrId = myBT(baseName) 'Drawing has the block
        If BtrId.IsNull Then 'Drawing does not have the block, read the drawuing file to create the block
            Try
                Using db As Database = New Database(False, True)
                    Dim myDB As Database = myDwg.Database
                    db.ReadDwgFile(BlockName, IO.FileShare.Read, True, "") 'Read the drawing file
                    BtrId = myDB.Insert(baseName, db, False) 'Insert drawing file as block into database
                End Using
            Catch ex As Autodesk.AutoCAD.Runtime.Exception
                MsgBox(ex.ToString)
            End Try
        End If
        ' Create the block reference in the drawing, and add the attribute text
        myBTR = myBT(BlockTableRecord.ModelSpace).GetObject(OpenMode.ForWrite)
        Dim bref As New DatabaseServices.BlockReference(InsPt, myBT(baseName))
        bref.Rotation = rotation
        bref.ScaleFactors = New Scale3d(scale, scale, scale)
        myBTR.AppendEntity(bref)
        smdTrans.AddNewlyCreatedDBObject(bref, True)
        Dim bDef As BlockTableRecord = smdTrans.GetObject(myBT(baseName), OpenMode.ForRead)
        'Set the Attribute Value
        If bDef.HasAttributeDefinitions Then
            Dim myEnt As DatabaseServices.Entity
            For Each id As ObjectId In bDef
                myEnt = id.GetObject(OpenMode.ForRead)
                If TypeOf myEnt Is AttributeDefinition Then
                    Dim myAttDef As AttributeDefinition = myEnt
                    'Create attribute reference based on the attributeDefinition
                    Dim attrRef As New AttributeReference
                    attrRef.SetAttributeFromBlock(myAttDef, bref.BlockTransform)
                    attrRef.TextString = RefDes
                    bref.AttributeCollection.AppendAttribute(attrRef)
                    smdTrans.AddNewlyCreatedDBObject(attrRef, True)
                End If
            Next
        End If
        smdTrans.Commit()

        smdTrans.Dispose()
        smdTransMan.Dispose()
    End Sub

 

 

Message 17 of 20
brian.k.smith
in reply to: mgorecki

Thanks for including the scale and rotation parameters!

Message 18 of 20

When I do this, it renames my block when it's inserted into the blocktable  and cannot find the reference.  At first I noticed I had a nested block.  So, I exploded the other block and purged it and saved it.  But it's still trying to insert the block with the nested block name instead of the block name.

Message 19 of 20

I found out that I had a setting set to true instead of false.

However, I still cannot read the attributes from my block.  Infact, when I double-click the block, I do not get the attribute list.  I get some odd window.

Message 20 of 20
norman.yuan
in reply to: mgorecki

You really should post your question as a new thread where you can provide more details of the issues you have, including your code. 

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Autodesk DevCon in Munich May 28-29th


Autodesk Design & Make Report

”Boost