How to iterate through a particular named block, type and then update only that have a certain value?
I had a request to make a plugin that found every blocked called "TY-I-Generic" that has Type: "GENERIC" with Value: DC and change it to Value: MC-1.
So I adjusted some code I found here:
http://forums.autodesk.com/autodesk/attachments/autodesk/152/16859/1/AttributesVb.txt
as this...
Public Function SetAttributeValue(ByVal btr As BlockTableRecord, ByVal tag As String, ByVal oldValue As String, ByVal newValue As String) As Integer Dim cnt As Integer = 0 Using tr As Transaction = btr.Database.TransactionManager.StartTransaction Dim ids As ObjectIdCollection = btr.GetBlockReferenceIds(True, True) If (Not ids Is Nothing) Then Dim btrId As ObjectId For Each btrId In ids Dim blockref As BlockReference = TryCast(btrId.GetObject(OpenMode.ForRead, False), BlockReference) If (Not blockref.AttributeCollection Is Nothing) Then Dim attId As ObjectId For Each attId In blockref.AttributeCollection Dim att As AttributeReference = TryCast(attId.GetObject(OpenMode.ForRead, False), AttributeReference) If String.Equals(att.Tag, tag, StringComparison.OrdinalIgnoreCase) Then If att.TextString = oldValue Then Try att.UpgradeOpen() Catch ex As Global.Autodesk.AutoCAD.Runtime.Exception If (ex.ErrorStatus <> ErrorStatus.OnLockedLayer) Then Throw End If Exit For End Try att.TextString = newValue att.DowngradeOpen() cnt += 1 Exit For End If End If Next End If Next End If tr.Commit() End Using Return cnt End Function
then a button to trigger it
Private Sub btnChange_Click(sender As System.Object, e As System.EventArgs) Handles btnChange.Click Dim sOldValue As String sOldValue = txtValueFrom.Text Dim sNewValue As String sNewValue = txtValueTo.Text Dim sBlockName As String sBlockName = txtBlock.Text Dim doc As Document = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument Dim id As ObjectId = GetBlockTableRecordId(doc.Database, sBlockName) If id.IsNull Then doc.Editor.WriteMessage(vbNewLine & "Block {0} not found.", New Object() {sBlockName}) Else Using tr As Transaction = doc.TransactionManager.StartTransaction Dim btr As BlockTableRecord = TryCast(id.GetObject(OpenMode.ForRead), BlockTableRecord) Dim cnt As Integer = SetAttributeValue(btr, Tag, sOldValue, sNewValue) doc.Editor.WriteMessage(vbNewLine & "Updated {0} attributes", New Object() {cnt}) tr.Commit() End Using End If MsgBox("Finished changing.") End Sub
sOldValue = "DC"
sNewValue = "MC-1"
sBlock = "TY-I-GENERIC"
are all pulled from text boxes...
And if I do SetAttributeValue(btr, Tag, sOldValue, sNewValue) that should do it but no... Currently with the above I do not get an error but no change either.
Also does this need to take into account the Type: DEVICE? Do I need to change this code so that it only finds block: TY-I-GENERIC and its Type: DEVICE and changes the value MC-1 to TY-I-GENERIC?
Is there anything else I can do to change the syntax to make sure this is correct?
Thank you in advance.
Solved! Go to Solution.
Solved by norman.yuan. Go to Solution.
Did you step through the function SetAttributeValue() in debugging? If you do, you can actually tell how many BlockReferences, and how many Attrubutereferences in each BlockReference the code loops through. If you place a break point at
att.TextString=newValue
and the break point is hit in debugging, you would have known that the attrbute value did get updated.
To me, your code looks fine (as long as the block IS NOT dynamic block). Simply stepping through the code iin debugging would easily tells you why the code works or not works as expected.
If the variable "cnt" is 0 after calling SetAttributeValue() and the block definition is a dynamic block, then that is because all the block references have their dynamic property or properties set to values that is different from the value in the block definition, thus these blockreferences cannot be found by BlockTableRecord.GetBlockReferenceIds(). You need to call BlockTableRecord.GetAnonymousBlockIds() and then for each anonymous block Id, you get anonymous BlockTableRecord, then call GetBlockReferenceId() on each anonymous BlockTableRecord.
Norman Yuan
Hi Norman, thank you for your reply.
It would be great if I could debug this but not been successful at attempting yet.
I've followed these:
step-by-step
http://usa.autodesk.com/adsk/servlet/index?siteID=123112&id=18171398
video
http://usa.autodesk.com/adsk/servlet/index?siteID=123112&id=18172834
But when I go to debug I get an error - (Please see Images NotAbleToDebug.png)
The breakpoint will not be hit.
Please also note NotAbleTodebug2 - that shows I have the debug spot on according to the video.
Any ideas how I may be able to debug this?
So, your code is executed froom a dialog form (a button clicked, for example)?
If so, it is better to better structure your code so that the AutoCAd operation, such as your code shown in the post, from the form's code behind. So you do not hit the FIBERWORLD issue with AutoCAD 2014 oor older version when debugging code with Windows form. This is very well-known issue. You need to set system variable "NEXTFIBERWORLD" to 0 and restart AutoCAD, so that the debugging break point set in the form's code behind can be hit.
Just be care, once FIBERWORLD is set to 0, the menu/ribbon would stop working. You need to set it back to 1 after you debugging code on Windows form. Again, structure your code to seperate UI with AutoCAD operation is the best practice to follow, if you can.
Norman Yuan
So I got the fiber issue with Autodesk worked out and now able to test the coding...
When I get to the function SetAttributeValue and set a break point...
Dim ids As ObjectIdCollection = btr.GetBlockReferenceIds(True, True)
If (Not ids Is Nothing) Then
Dim btrId As ObjectId
For Each btrId In ids
...
It just goes to Next.
It finds the block but does get any blockreferenceIds (please note image provided as attached)
So, if BlockTableRecord.GetBlockReferenceIds() returns a empty ObjectIdCollection, it means there is no BlockReference based on this block definition exists in this drawing; or if the block is dynamic block, then you need to find corresponding anonymous BlockTableRecords, then all the BlockReferences based on these anonymous blocktablerecord (as I mentioned in my previous reply. Is the block a Dynamic block?).
Norman Yuan
I made a some quick change to your SetAttributeValue(), not tested, right after the line "Using tr As Transaction....":
Public Function SetAttributeValue(ByVal btr As BlockTableRecord, _ ByVal tag As String, _ ByVal oldValue As String, _ ByVal newValue As String) As Integer Dim cnt As Integer = 0 Using tr As Transaction = btr.Database.TransactionManager.StartTransaction '' get all blockreferences' Id Dim brefIds As List(Of ObjectId) = New List(Of ObjectId) Dim ids As ObjectIdCollection = btr.GetBlockReferenceIds(True, True) For Each id As ObjectId In ids brefIds.add(id) Next '' get all dynamic blockreferences If btr.IsDynamicBlock Then Dim brIds As ObjectIdCollection = btr.GetAnonymousBlockIds() For Each id As ObjectId In brIds Dim br As BlockTableRecord = _ DirectCast(tr.GetObject(id, OpenMode.ForRead), BlockTableRecord) ids = br.GetBlockReferenceIds(True, True) For Each id As ObjectId In ids brefIds.Add(id) Next Next End If If (brefIds.Count > 0) Then For Each btrId As ObjectId In brefIds Dim blockref As BlockReference = _ TryCast(btrId.GetObject(OpenMode.ForRead, False), BlockReference) If (Not blockref.AttributeCollection Is Nothing) Then Dim attId As ObjectId For Each attId In blockref.AttributeCollection Dim att As AttributeReference = _ TryCast(attId.GetObject(OpenMode.ForRead, False), AttributeReference) If String.Equals(att.Tag, tag, StringComparison.OrdinalIgnoreCase) Then If att.TextString = oldValue Then Try att.UpgradeOpen() Catch ex As Global.Autodesk.AutoCAD.Runtime.Exception If (ex.ErrorStatus <> ErrorStatus.OnLockedLayer) Then Throw End If Exit For End Try att.TextString = newValue att.DowngradeOpen() cnt += 1 Exit For End If End If Next End If Next End If tr.Commit() End Using Return cnt End Function
Norman Yuan
I got one last error.
Variable id hides a variable in an enclosing block.
Please note attachment:
I do not understand why it doesn't show for the first instance of its use....4 lines above it.
Change this:
'' get all dynamic blockreferences
If btr.IsDynamicBlock Then
Dim brIds As ObjectIdCollection = btr.GetAnonymousBlockIds()
For Each id As ObjectId In brIds
Dim br As BlockTableRecord = _
DirectCast(tr.GetObject(id, OpenMode.ForRead), BlockTableRecord)
ids = br.GetBlockReferenceIds(True, True)
For Each id As ObjectId In ids
brefIds.Add(id)
Next
Next
End If
To This:
'' get all dynamic blockreferences
If btr.IsDynamicBlock Then
Dim brIds As ObjectIdCollection = btr.GetAnonymousBlockIds()
For Each id As ObjectId In brIds
Dim br As BlockTableRecord = _
DirectCast(tr.GetObject(id, OpenMode.ForRead), BlockTableRecord)
ids = br.GetBlockReferenceIds(True, True)
For Each idd As ObjectId In ids
brefIds.Add(idd)
Next
Next
End If
Norman Yuan
That worked great. Thank you sincerely for helping me with this.
It is much appreciated.
Can't find what you're looking for? Ask the community or share your knowledge.