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
Solved! Go to Solution.
Solved by norman.yuan. Go to Solution.
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,...)
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
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)
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
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."
Sorry, there was a typo:
GetFileNameWothout...() SHOULD HAVE BEEN GetFileNameWithout...()
Norman Yuan
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
Norman Yuan
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.
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
Unfortunately, the blocks being read in, still do not have usable attributes. Also, I cannot insert any of them manually.
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
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);
....
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?
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
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
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.
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.
You really should post your question as a new thread where you can provide more details of the issues you have, including your code.
Norman Yuan
Can't find what you're looking for? Ask the community or share your knowledge.