Hi,
I`ve written some code that changes the layer of selected blocks & nested blocks. Blockreference(s) containing attributes needs to be synced (attsync). What`s the best way of doing an attsync in .Net? I`ve look at the following Brute force method from Gile (NET BLOCK Routines), but I`m getting some intellisense error:
'GetAttributes' is not a member of 'BlockTableRecord'
'ResetAttributes' is not a member of 'BlockReference'
My target framework in set to 4.5
My AcCoreMgd, AcDbMgd & AcDbMgd are on version 20.1.0.0
Any help is appreciated!
Solved! Go to Solution.
Solved by _gile. Go to Solution.
Hi,
It looks like you didn't copy the whole code from TheSwamp: the GetAttributes() and ResetAttributes() methods are missing in the code you show.
With VB, you have to clear the Root Namespace in the project to add these extension methods to Autodesk.AutoCAD.DatabaseServices.
Imports Autodesk.AutoCAD.DatabaseServices Imports Autodesk.AutoCAD.Runtime Namespace Autodesk.AutoCAD.DatabaseServices Public Module ExtensionMethods Dim attDefClass As RXClass = RXClass.GetClass(GetType(AttributeDefinition)) <System.Runtime.CompilerServices.Extension> _ Public Sub SynchronizeAttributes(target As BlockTableRecord) If target Is Nothing Then Throw New ArgumentNullException("target") End If Dim tr As Transaction = target.Database.TransactionManager.TopTransaction If tr Is Nothing Then Throw New Exception(ErrorStatus.NoActiveTransactions) End If Dim attDefs As List(Of AttributeDefinition) = target.GetAttributes(tr) For Each id As ObjectId In target.GetBlockReferenceIds(True, False) Dim br As BlockReference = _ DirectCast(tr.GetObject(id, OpenMode.ForWrite), BlockReference) br.ResetAttributes(attDefs, tr) Next If target.IsDynamicBlock Then target.UpdateAnonymousBlocks() For Each id As ObjectId In target.GetAnonymousBlockIds() Dim btr As BlockTableRecord = _ DirectCast(tr.GetObject(id, OpenMode.ForRead), BlockTableRecord) attDefs = btr.GetAttributes(tr) For Each brId As ObjectId In btr.GetBlockReferenceIds(True, False) Dim br As BlockReference = _ DirectCast(tr.GetObject(brId, OpenMode.ForWrite), BlockReference) br.ResetAttributes(attDefs, tr) Next Next End If End Sub <System.Runtime.CompilerServices.Extension> _ Private Function GetAttributes(target As BlockTableRecord, tr As Transaction) As List(Of AttributeDefinition) Dim attdefs As List(Of AttributeDefinition) = New List(Of AttributeDefinition) For Each id As ObjectId In target If id.ObjectClass = attDefClass Then Dim attDef As AttributeDefinition = _ DirectCast(tr.GetObject(id, OpenMode.ForRead), AttributeDefinition) attdefs.Add(attDef) End If Next Return attdefs End Function <System.Runtime.CompilerServices.Extension> _ Private Sub ResetAttributes(br As BlockReference, attDefs As List(Of AttributeDefinition), tr As Transaction) Dim attValues As New Dictionary(Of String, String)() For Each id As ObjectId In br.AttributeCollection If Not id.IsErased Then Dim attRef As AttributeReference = _ DirectCast(tr.GetObject(id, OpenMode.ForWrite), AttributeReference) attValues.Add( _ attRef.Tag, _ If(attRef.IsMTextAttribute, attRef.MTextAttribute.Contents, attRef.TextString)) attRef.Erase() End If Next For Each attDef As AttributeDefinition In attDefs Dim attRef As New AttributeReference() attRef.SetAttributeFromBlock(attDef, br.BlockTransform) If attDef.Constant Then attRef.TextString = If(attDef.IsMTextAttributeDefinition, _ attDef.MTextAttributeDefinition.Contents, _ attDef.TextString) Else If attValues IsNot Nothing AndAlso attValues.ContainsKey(attDef.Tag) Then attRef.TextString = attValues(attDef.Tag.ToUpper()) End If br.AttributeCollection.AppendAttribute(attRef) tr.AddNewlyCreatedDBObject(attRef, True) Next End Sub End Module End Namespace
Looks like you are missing some extension method(s)
an internet search pulled up with these results:
https://www.theswamp.org/index.php?topic=42591.0
it's in c# i'm sure you'd be able to find the exact ones in VB.NET with a good google search.
best rgds
@BKSpurgeon, the whole code (C# and VB) is in the message linked by @JFN_KSH:
http://www.theswamp.org/index.php?topic=31859.msg508802#msg508802
Thanks Gile, it's all good now!! This code is genius! You got my admiration... I don't know why I removed theses lines of code... Thanks again.
Hi Gile,
Firstly let me say thank you for sharing your code, it's helped me enormously.
I have discovered a minor problem.
Steps to reproduce:
AutoCAD then displays a message: Error, Object was erased. The block's rotation is set to 0 but the attributes remain where they were before the rotation back to 0.
I was wondering if you could confirm whether you see the same behaviour and if not, do you have any suggestions as to what might be causing the problem?
This is how I'm calling it:
''' <summary> ''' Synchronise block reference attributes with their definitions ''' </summary> ''' <param name="blkRefIds">The objectIds of the block references to synchronise</param> Public Shared Sub myAttSync(blkRefIds As List(Of ObjectId)) Dim db As Database = HostApplicationServices.WorkingDatabase Dim doc As Document = Application.DocumentManager.GetDocument(db) Using docLock As DocumentLock = doc.LockDocument() Using trans As Transaction = db.TransactionManager.StartTransaction()
' If no specific blocks supplied then synchronise all... If blkRefIds Is Nothing Then Dim bt As BlockTable = trans.GetObject(db.BlockTableId, OpenMode.ForRead) blkRefIds = bt.Cast(Of ObjectId)().ToList() End If For Each id As ObjectId In blkRefIds Dim btr As BlockTableRecord = trans.GetObject(id, OpenMode.ForRead) If btr.IsLayout Then Continue For End If If btr.IsFromExternalReference Then Continue For End If If btr.GetBlockReferenceIds(True, True).Count = 0 Then Continue For End If btr.UpgradeOpen() btr.SynchronizeAttributes() Next trans.Commit() End Using End Using End Sub
Thanks
Paul
I think I've fixed it.
I've changed ResetAttributes to look like this so rather than deleting each one and recreating it, it simply updates them from the definition:
<System.Runtime.CompilerServices.Extension> Private Sub ResetAttributes(br As BlockReference, attdefs As List(Of AttributeDefinition), trans As Transaction) Dim attValues As New Dictionary(Of String, String)() For Each id As ObjectId In br.AttributeCollection If Not id.IsErased Then Dim attRef As AttributeReference = DirectCast(trans.GetObject(id, OpenMode.ForWrite), AttributeReference) Dim attDef As AttributeDefinition = attdefs.Find(Function(x) x.Tag = attRef.Tag) If attDef Is Nothing Then attRef.Erase() Continue For End If Dim attText As String = If(attRef.IsMTextAttribute, attRef.MTextAttribute.Contents, attRef.TextString) attRef.SetAttributeFromBlock(attDef, br.BlockTransform) attRef.TextString = attText attdefs.Remove(attDef) End If Next For Each attDef As AttributeDefinition In attdefs Dim attRef As New AttributeReference() attRef.SetAttributeFromBlock(attDef, br.BlockTransform) If attDef.Constant Then attRef.TextString = If(attDef.IsMTextAttributeDefinition, attDef.MTextAttributeDefinition.Contents, attDef.TextString) ElseIf attValues IsNot Nothing AndAlso attValues.ContainsKey(attDef.Tag) Then attRef.TextString = attValues(attDef.Tag.ToUpper()) End If br.AttributeCollection.AppendAttribute(attRef) trans.AddNewlyCreatedDBObject(attRef, True) Next End Sub
This also handles blocks that have duplicate attributes. I know it doesn't make sense for attributes to be duplicated but AutoCAD allows users to create duplicates, and in my experience if they can do it, they will!
Can you see any flaws in the above?
Thanks again
Paul
Update:
There was an issue with the code I posted. It would only work for the first instance of a particular block. The second one would get its attributes removed. It now looks like this:
<System.Runtime.CompilerServices.Extension> Private Sub ResetAttributes(br As BlockReference, attdefs As List(Of AttributeDefinition), trans As Transaction) Dim defsCopy As New List(Of AttributeDefinition)(attdefs) Dim attValues As New Dictionary(Of String, String)() For Each id As ObjectId In br.AttributeCollection If Not id.IsErased Then Dim attRef As AttributeReference = DirectCast(trans.GetObject(id, OpenMode.ForWrite), AttributeReference) Dim attDef As AttributeDefinition = attdefs.Find(Function(x) x.Tag = attRef.Tag) If attDef Is Nothing Then attRef.Erase() Continue For End If Dim attText As String = If(attRef.IsMTextAttribute, attRef.MTextAttribute.Contents, attRef.TextString) attRef.SetAttributeFromBlock(attDef, br.BlockTransform) attRef.TextString = attText defsCopy.Remove(attDef) End If Next For Each attDef As AttributeDefinition In defsCopy Dim attRef As New AttributeReference() attRef.SetAttributeFromBlock(attDef, br.BlockTransform) If attDef.Constant Then attRef.TextString = If(attDef.IsMTextAttributeDefinition, attDef.MTextAttributeDefinition.Contents, attDef.TextString) ElseIf attValues IsNot Nothing AndAlso attValues.ContainsKey(attDef.Tag) Then attRef.TextString = attValues(attDef.Tag.ToUpper()) End If br.AttributeCollection.AppendAttribute(attRef) trans.AddNewlyCreatedDBObject(attRef, True) Next End Sub
Hello Giles. I'm trying to apply your Attsync solution, but I can not make it work in multithreading or Backgroundworker. It's not possible? I know that Autocad has a bad behavior with multithreading. Is this the cause? When I run it as a simple method, everything works fine.
Thank's
José
Can't find what you're looking for? Ask the community or share your knowledge.