TheSwamp
Code Red => .NET => Topic started by: Bert on September 03, 2013, 08:06:41 AM
-
Hello guys,
Been stuck for a couple of hours on the job and was wondering if any of you could help me ?
(I bet you can :wink: )
What i'm doing:
- Have a 'master dwg' that has relevant Blocks & Dynamic Blocks
- Running a routine that populates a new production drawing's modelspace with BlockReferences, importing from the 'master dwg' if the block is not yet present in the new production drawing.
- Importing 'regular' & Dynamic blocks alike.
- Able to set attributes and dynamic properties where and when needed
- After this initial insertion-method, all blocks are inserted and dimensioned in place, a certain check needs to be made.
I traverse the ModelSpace and based on the BlockRef.Name a certain action needs to be performed
- This works for my regular blocks, but the Dynamic ones all have those anonymous names, like "*U24", making them dodge my name-check.
found a solution to get the 'EffectiveName' of a dynamicblock :
Public Shared Function EffectiveName(ByVal blkref As BlockReference) As String
If blkref.IsDynamicBlock Then
Return DirectCast(blkref.DynamicBlockTableRecord.GetObject(OpenMode.ForRead), BlockTableRecord).Name
Else
Return blkref.Name
End If
End Function
But problem is : blkref.IsDynamicBlock allways returns false !
So even when my Dynamic blocks in ModelSpace are inserted well, have attributes and dynamic properties correctly set-up and function well progammatically they are not recognised as being Dynamic Blocks ?
Why would this be ?
-
Not seeing more of your code as for where the Blockreference.IsDynamicBlock is called, I'd be almost certain that the call is made in such way: you obtain a Blockreference object in a transaction, and then pass the BlockReference object around (for getting its "effective name") outside that transaction where the BlockReference is obtained. That is why IsDynamicBlock returns false, even you do know that the block is a dynamic block reference.
Passing object obtained via Transaction is not good and obtaining DBObject's properties outside a transaction scope may not get you what you want, as you just experienced. Instead, you ALWAYS pass ObjectID between transaction, and obtain DBObject's properties within the scope of active transaction.
-
Hey n.yuan, thanks for replying !
This is how i'm doing this (code-wise):
Step 1) Inserting my Dynamic Blocks :
...
' assure the desired doorsketchBlock is present in the blocktable
acadBlockId = Acad.searchOrImportBlock(Acad.SketchblocksFileName, blockName)
' insert the block in modelspace
acadblockref = Acad.InsertBlock(blockName, insPnt, 1, 1, 1, _acParentBlockref.Rotation)
'Set Dynamic properties
AttVals.Clear()
AttVals.Add("Lookup|" & dimNuttigeBreedte & " x " & dimPaneelBreedte)
AttVals.Add("Paneeldikte|" & _acParentBlockref.ScaleFactors.Y)
AttVals.Add("Draairichting|" & conDraairichting)
Acad.set_DynamicBlockProperties(acadblockref, AttVals)
'Set Attributes
AttVals.Clear()
AttVals.Add("InventTransId|" & genInventTransId)
AttVals.Add("DIM|" & dimNuttigeBreedte & "|" & dimPaneelBreedte & "|" & dimNuttigeHoogte & "|" & _acParentBlockref.ScaleFactors.Z & "|" & doorPanelConn)
AttVals.Add("ONDERBOUW|" & IIf(conVerzonkenOpstelling, _
Acad.get_AttValByIdx(_acParentBlockref, 3) & "|" & conVerzonkenOpstelling, _
Acad.get_AttValByIdx(_acParentBlockref, 3)))
Acad.set_BlockAttributes(acadblockref, AttVals)
...
4 methods come to play in the above code :
Acad.searchOrImportBlock (checks if the desired block is allready present in the current drawings blocktable, if not it'll fetch it from the 'master drawing')
Acad.InsertBlock (knows that the desired block is present in the current drawing's blocktable, it'll insert it in modelspace as and return a BlockReference object)
Acad.set_DynamicBlockProperties (made to set Dynamic Properties of the given BlockReference)
Acad.set_BlockAttributes (made to set attribute values of the given BlockReference)
Acad.searchOrImportBlock
Shared Function searchOrImportBlock(_sourceFileName As String, _blockName As String) As ObjectId
' Get the current document and database
Dim acDoc As Document = Application.DocumentManager.MdiActiveDocument
Dim acCurDb As Database = acDoc.Database
' Lock the document
Using acLckDoc As DocumentLock = acDoc.LockDocument()
' Start a transaction in the database
Using acTrans As Transaction = acCurDb.TransactionManager.StartTransaction()
' Get the Block table for the current database
Dim acBlockTbl As BlockTable = acTrans.GetObject(acCurDb.BlockTableId, OpenMode.ForRead)
' If found in local BlockTableRecord
If acBlockTbl.Has(_blockName) Then
Return acBlockTbl(_blockName)
End If
End Using ' Dispose of the transaction
End Using ' Unlock the document
' If not found in local BlockTableRecord then Import from _sourceFileName
Return importBlockFromSourceFile(_sourceFileName, _blockName)
End Function
Shared Function importBlockFromSourceFile(_sourceFileName As String, _blockName As String) As ObjectId
Dim sourceDb As New Database(False, True)
Dim acObjIdColl As ObjectIdCollection = New ObjectIdCollection()
' Read the DWG into a side database
sourceDb.ReadDwgFile(_sourceFileName, System.IO.FileShare.Read, True, "")
Using acTrans As Transaction = sourceDb.TransactionManager.StartTransaction()
' Open the block table
Dim bt As BlockTable = DirectCast(acTrans.GetObject(sourceDb.BlockTableId, OpenMode.ForRead, False), BlockTable)
If bt.Has(_blockName) Then
acObjIdColl.Add(bt(_blockName))
'SendCommandMessage(vbCrLf & "Imported block: '" & _blockName & "' from '" & _sourceFileName & "'" & vbCrLf)
End If
'' Check each block in the block table
'For Each btrId As ObjectId In bt
' Dim btr As BlockTableRecord = DirectCast(acTrans.GetObject(btrId, OpenMode.ForRead, False), BlockTableRecord)
' If btr.Name = _blockName Then
' acObjIdColl.Add(btr.ObjectId)
' SendCommandMessage(vbCrLf & "Importing block: " & btr.Name)
' End If
' btr.Dispose()
'Next
End Using
Dim acDoc As Document = Application.DocumentManager.MdiActiveDocument
Dim acCurDb As Database = acDoc.Database
' Lock the document
Using acLckDoc As DocumentLock = acDoc.LockDocument()
' Start a transaction in the database
Using acTrans = acDoc.TransactionManager.StartTransaction()
' Open the Block table for read
Dim acBlkTblNewDoc As BlockTable
acBlkTblNewDoc = acTrans.GetObject(acCurDb.BlockTableId, OpenMode.ForRead)
' Open the Block table record Model space for read
Dim acBlkTblRecNewDoc As BlockTableRecord
acBlkTblRecNewDoc = acTrans.GetObject(acBlkTblNewDoc(BlockTableRecord.ModelSpace), OpenMode.ForRead)
' Clone the objects to the new database
Dim acIdMap As IdMapping = New IdMapping()
acCurDb.WblockCloneObjects(acObjIdColl, acBlkTblRecNewDoc.ObjectId, acIdMap, DuplicateRecordCloning.Ignore, False)
' Commit the transaction
acTrans.Commit()
End Using ' Dispose of the transaction
End Using ' Unlock the document
'Return ObjectId
Return acObjIdColl(0)
End Function
Acad.InsertBlock
Shared Function InsertBlock(ByVal _blockName As String, ByVal insPt As Point3d, ByVal xBlkScale As Double, _
ByVal yBlkScale As Double, ByVal zBlkScale As Double, ByVal ang As Double) As BlockReference
' Get the current document and database
Dim acDoc As Document = Application.DocumentManager.MdiActiveDocument
Dim acCurDb As Database = acDoc.Database
' Lock the document
Using acLckDoc As DocumentLock = acDoc.LockDocument()
' Start a transaction in the database
Using acTrans = acDoc.TransactionManager.StartTransaction()
Dim blkTable As BlockTable = acTrans.GetObject(acCurDb.BlockTableId, OpenMode.ForRead)
If blkTable.Has(_blockName) Then
Dim blkObjId As ObjectId = blkTable(_blockName)
Dim blkRef As BlockReference = New BlockReference(insPt, blkObjId)
blkRef.SetDatabaseDefaults()
blkRef.ScaleFactors = New Scale3d(xBlkScale, yBlkScale, zBlkScale)
blkRef.Rotation = ang
Dim blkTblRec As BlockTableRecord
' Assumes the current space was already changed to.
blkTblRec = acTrans.GetObject(acCurDb.CurrentSpaceId, OpenMode.ForWrite)
blkTblRec.AppendEntity(blkRef)
acTrans.AddNewlyCreatedDBObject(blkRef, True)
' add the attribute definitions.
Dim blkTblR As BlockTableRecord = blkObjId.GetObject(OpenMode.ForRead)
For Each objId As ObjectId In blkTblR
Dim obj As DBObject = objId.GetObject(OpenMode.ForRead)
If TypeOf obj Is AttributeDefinition Then
Dim ad As AttributeDefinition = objId.GetObject(OpenMode.ForRead)
Dim ar As AttributeReference = New AttributeReference()
ar.SetAttributeFromBlock(ad, blkRef.BlockTransform)
ar.Position = ad.Position.TransformBy(blkRef.BlockTransform)
blkRef.AttributeCollection.AppendAttribute(ar)
acTrans.AddNewlyCreatedDBObject(ar, True)
End If
Next
' Commit the transaction
acTrans.Commit()
Return blkRef
End If
End Using ' Dispose of the transaction
End Using ' Unlock the document
End Function
Acad.set_DynamicBlockProperties
Shared Sub set_DynamicBlockProperties(_blkRef As BlockReference, _attVals As List(Of String))
' Get the current document and database, and start a transaction
Dim acDoc As Document = Application.DocumentManager.MdiActiveDocument
Dim acCurDb As Database = acDoc.Database
' Lock the new document
Using acLckDoc As DocumentLock = acDoc.LockDocument()
' Start a transaction in the new database
Using acTrans = acDoc.TransactionManager.StartTransaction()
'Open _blkRef For writing
_blkRef = DirectCast(acTrans.GetObject(_blkRef.Id, OpenMode.ForWrite), BlockReference)
For Each dynamicProperty As DynamicBlockReferenceProperty In _blkRef.DynamicBlockReferencePropertyCollection
Dim recievedValue As String = get_AttVal(dynamicProperty.PropertyName, _attVals)
If Not String.IsNullOrEmpty(recievedValue) Then
If is_DynamicValueAllowed(recievedValue, dynamicProperty) Then
If IsNumeric(recievedValue) Then
dynamicProperty.Value = CDbl(recievedValue)
Else
dynamicProperty.Value = recievedValue
End If
Else
MsgBox("Voor DynamicBlock '" & _blkRef.Name & "'" & _
" werd voor property '" & dynamicProperty.PropertyName & "'" & _
" getracht de niet-toegestane waarde '" & recievedValue & "' mee te geven." & _
vbLf & vbLf & _
"Gelieve dit te controleren !", vbOKOnly + MsgBoxStyle.Exclamation)
End If
End If
Next
' Commit the transaction
acTrans.Commit()
End Using ' Dispose of the transaction
End Using ' Unlock the document
End Sub
Acad.set_BlockAttributes
Shared Sub set_BlockAttributes(_blkRef As BlockReference, _attVals As List(Of String))
' Get the current document and database, and start a transaction
Dim acDoc As Document = Application.DocumentManager.MdiActiveDocument
Dim acCurDb As Database = acDoc.Database
' Lock the new document
Using acLckDoc As DocumentLock = acDoc.LockDocument()
' Start a transaction in the new database
Using acTrans = acDoc.TransactionManager.StartTransaction()
Dim attCol As AttributeCollection = _blkRef.AttributeCollection
For Each attId As ObjectId In attCol
Dim attRef As AttributeReference = DirectCast(acTrans.GetObject(attId, OpenMode.ForWrite), AttributeReference)
Dim recievedValue As String = get_AttVal(attRef.Tag, _attVals)
If Not String.IsNullOrEmpty(recievedValue) Then attRef.TextString = recievedValue
Next
' Commit the transaction
acTrans.Commit()
End Using ' Dispose of the transaction
End Using ' Unlock the document
End Sub
I'm not taking any credit for the above code. None of it is completely compy-pasted, neighter is it completely written by hand.
Step 2) Traverse inserted blocks :
I've made a collection of all BlockReferences in the 'Sketch'-layer and now i want to evaluate them, and take action based on their name
'Perform individual convertion per block in the SketchBlocksCollection, based on it's type (Name)
For Each acSketchBlockRef As BlockReference In SketchBlocksCollection
Select Case acSketchBlockRef.Name
Case "SketchHoekBlock V"
convert_Corner_vertical(acSketchBlockRef, SketchBlocksCollection)
Case "SketchVloerBlock"
Dim MaxUsedFloorPanelLenght = convert_Floor(acSketchBlockRef, SketchBlocksCollection, SketchConversionSettings, sketchOrigins, MaxUsedLength)
MaxUsedLength = Math.Max(MaxUsedLength, MaxUsedFloorPanelLenght)
Case "SketchPlafondBlock"
Dim MaxUsedCielingPanelLenght = convert_Cieling(acSketchBlockRef, SketchBlocksCollection, SketchConversionSettings, sketchOrigins, MaxUsedLength)
MaxUsedLength = Math.Max(MaxUsedLength, MaxUsedCielingPanelLenght)
Case "SketchPaneelBlock"
convert_Wall(acSketchBlockRef, SketchBlocksCollection, SketchConversionSettings, SketchHorizontalCornerBlocksCollection, SketchDeurBlocksCollection)
[b]Case "SketchDraaiDeurBlock", "SketchDraaiDeurDubbelBlock", "SketchSchuifDeurBlock"[/b]
convert_Door(acSketchBlockRef) 'code never reached
Case "SketchVBNBlock"
convert_VBN(acSketchBlockRef, SketchConversionSettings, sketchOrigins)
Case Else
End Select
Next acSketchBlockRef
Here it goes wrong: the
Case "SketchDraaiDeurBlock", "SketchDraaiDeurDubbelBlock", "SketchSchuifDeurBlock"
is never met. As all blocks with these 'names' are Dynamic Blocks.
Thus acSketchBlockRef.Name evaluates to (example) "*U24", not "SketchDraaiDeurBlock".
I tried this :
For Each acSketchBlockRef As BlockReference In SketchBlocksCollection
Select Case[b] EffectiveName(acSketchBlockRef)[/b]
Case "SketchHoekBlock V"
convert_Corner_vertical(acSketchBlockRef, SketchBlocksCollection)
Case "SketchVloerBlock"
Dim MaxUsedFloorPanelLenght = convert_Floor(acSketchBlockRef, SketchBlocksCollection, SketchConversionSettings, sketchOrigins, MaxUsedLength)
MaxUsedLength = Math.Max(MaxUsedLength, MaxUsedFloorPanelLenght)
Case "SketchPlafondBlock"
Dim MaxUsedCielingPanelLenght = convert_Cieling(acSketchBlockRef, SketchBlocksCollection, SketchConversionSettings, sketchOrigins, MaxUsedLength)
MaxUsedLength = Math.Max(MaxUsedLength, MaxUsedCielingPanelLenght)
Case "SketchPaneelBlock"
convert_Wall(acSketchBlockRef, SketchBlocksCollection, SketchConversionSettings, SketchHorizontalCornerBlocksCollection, SketchDeurBlocksCollection)
Case "SketchDraaiDeurBlock", "SketchDraaiDeurDubbelBlock", "SketchSchuifDeurBlock"
convert_Door(acSketchBlockRef)
Case "SketchVBNBlock"
convert_VBN(acSketchBlockRef, SketchConversionSettings, sketchOrigins)
Case Else
convert_Door(acSketchBlockRef)
End Select
Next acSketchBlockRef
with the method 'EffectiveName' beeing :
Public Shared Function EffectiveName(ByVal blkref As BlockReference) As String
If blkref.IsDynamicBlock Then
Return DirectCast(blkref.DynamicBlockTableRecord.GetObject(OpenMode.ForRead), BlockTableRecord).Name
Else
Return blkref.Name
End If
End Function
But here, blkref.IsDynamicBlock allways returns false.
pfew :| that was a mouthfull !
Any Ideas ?
-
A quick note, in your importBlockFromSourceFile() function you should create the side database in a using variable so you don't need to worry about its disposal (which you didn't do)
Regarding the problem you're having, could you show me what you're doing to create the SketchBlocksCollection you're iterating through?
-
Well, only the code you showed in "step 2" is relevant to the issue (the For Each ...Next with Select Case... in it). However, you did not show the whole context where the For Each...Next is in.
The For Each... Next would only works if it is in a Transaction scope and the blockreference in SketchBlockCollection in obtained within the transaction scope, something like this:
''somewhere you may have code like this to build your blockrerefence collection
Public Function GetSketchBlocks() As List of BlockReference
blocks As List of Blockreference=New ....
''You probably used one of the Editor.SelectXXX() methos
Using tran As Transaction=MyDb.TransactionManager.StartTransaction()
''Obtain Blockrefernce objects and add to StechBlockCollection here
blocks.Add(tran.GetObject(.....)
tran.Commit()
End Using
Return blocks
End Function
''Then you can run the For Each ... here
''At this stage, the Blockreference object is out of the transaction scope where it is obtained
''Therefore some properties are lost (such as IsDynamicBlock)
For Each blk As Blockreference in SketchCollection
Select Case EffectiveName().ToUpper()
....
End Select
Next
For that code structure, you probably thought that you'd better to make the code for gathering BlockReference into a collection in a separate method, so it could be reused somewhere else, like
Public Function GetSketchBlocks() As List of BlockReference
....
End Function
This is when the problem arise: you need a transaction to turn selected ObjectId into BlockReference in order to place them into the collection. And at the end of the method, the transaction is completed, and you pass DBObject(s) (BlockReference, in this case) to other methods for further processing. Then you have problem as I pointed out in previous reply: you should pass around ObjectId, instead of DBObject, and only access DBObject's properties in current transaction when needed.
So, you should have a method like this:
Public Function GetSketchBlocks() As ObjectIdCollection
....
'' Only returns ObjectId or a collection of ObjectId for other process to reuse this code
End Function
And in the place where you need to read BlockReference' properties, you do
''Get selelcted blocks as a collection of ObjectId, instead of DBObject
SktechBlocks=GetSketchBlocks()
''Loop through the ObjectId in a transaction
Using tran As Transaction=....()
For Each id As ObjectId in SketchBlockCollection
Dim BlkRef As BlockReference = tran.GetObject(id,...)
Select Case EffectiveName(blkReg)
....
End Select
Next
tran.Commit()
End Using
-
That makes hell'of lotsa sense !
I'm not @work anymore, but home now. I will re-do my code as you stated.
You're right, I do collect my SketchBlocks (Blockrefs) via a function that returns a 'blockref-collection'.
It's called 'collect_SketchBlocks' (or something like it, don't have it on me here)
and it just crawls the ModelSpace for Blockrefs that has 'SKETCH' as their layer, and when found adds them to the collection.
when done, ... it closes the transaction it was using.
Never tought that would be giving me this trouble ..
if I understand correctly i'll have to create the collection,
traverse trough it and
perform the needed actions
all under the eye of a 1 single transaction ?
I'm allready knee-deep in AutoCAD .NET api for weeks, and have done alot of nifty stuff.
Yet i still feel i've got lots to learn and sometimes i'm missing out on some key concepts. :-o :-(
.Net specific documentation i find to be very superficial.
I'll report back within 10hours with my findings !
Thanks for the pointers !
-
If you like to maintain per function transaction then you will need to wrap the subroutine call inside a transaction to keep the objects opened or pass ObjectIds around. If a transaction is absolutely required I like to use the top transaction from within the subroutine:
Function main()
...
Using Transaction tr = db.TransactionManager.StartTransaction()
Dim DbObjectCollection blocks = get_blocks(db)
sort_blocks(blocks)
...
End Using
End Function
Function get_Blocks(db As Database) As DbObjectCollection
Dim Transaction tr = db.TransactionManager.TopTransaction
If tr == null Then throw new ArgumentNullException( "Function called outside the scope of transaction" )
...get blocks
End Function
-
Looking quickly and too hard to follow, but do not see how your InsertBlock function works.
Looks like you create and add it to transaction then commit the transaction which would close the object then return the blockreference, and do not see that it is in a nested transaction to pass it up to.
-
What happens if you pass the blockReference byRef instead of byVal?
-
Looking quickly and too hard to follow, but do not see how your InsertBlock function works.
Looks like you create and add it to transaction then commit the transaction which would close the object then return the blockreference, and do not see that it is in a nested transaction to pass it up to.
I do, as I never realised this would be a problem.
I'm using a seperate *new* transaction each time I need to dig in. (Insert entities, edit Blocks, move things about)
Virtually every method I wrote in my code holds its 'own' transaction.
I understand very well how closing a transaction could pose a problem further down the line.
If you like to maintain per function transaction then you will need to wrap the subroutine call inside a transaction to keep the objects opened or pass ObjectIds around. If a transaction is absolutely required I like to use the top transaction from within the subroutine
Will use this approach.
In my defense, or in that of my code: Everything did work as designed tough !
If it was not for that check (.IsDynamic) that kept failing, i'd never be any wiser.
All other functionality actually worked (insertion, attributes, transformation and erasing 'regular' Blocks)
I'll adress to it now, will report back !
Thank you
-
I assume that following code-example works identically as what WILL HATCH suggests ?
(It involves passing the 'active' transaction trough an argument)
Function main()
...
Using Transaction tr = db.TransactionManager.StartTransaction()
Dim DbObjectCollection blocks = get_blocks(db, tr)
sort_blocks(blocks)
...
End Using
End Function
Function get_Blocks(db As Database, tr as Transaction) As DbObjectCollection
If tr == null Then throw new ArgumentNullException( "Function called outside the scope of transaction" )
...get blocks
End Function
Then again, adepting every method that uses a transaction to recieve a transaction via argument might not be so desireable.
*EDIT*
Using Transaction tr = db.TransactionManager.StartTransaction()
I obviously ment :
Dim Transaction tr = db.TransactionManager.TopTransaction
*/EDIT*
looks more acceptable
-
First off
Welcome to the Swamp Bert!
Are you using a vertical because I remember Will was using MEP or maybe something different but he figured out it would create a Transaction for each Custom Command.
Also Any method that requires or you want to use a transaction does not need to pass around a transaction.
If you step back and look there are only so many entry points AutoCAD will execute your code.
-Initialize
-Command
-Events
-Overrule
-etc...
And can change how it needs to be handled.
And if not paying attention using nested or numerous transactions you can only have data partially changed.
Need to get some sleep but when get a better idea of requirements someone can help refactoring code, and how you can wrap methods inside one transaction.
-
Thanks for the welcome Jeff !
What do you mean with me using a 'vertical' ?
As stated my code was realy overdue for some refactoring.
I followed WILL HATCH's advice, keeping all my operations in 1 transaction.
This took some work, but the results are as follows :
Sub Main()
' Get the current document and database
Dim acDoc As Document = Application.DocumentManager.MdiActiveDocument
Dim acCurDb As Database = acDoc.Database
' Lock the document
Using acLckDoc As DocumentLock = acDoc.LockDocument()
' Start a transaction
Using acTrans As Transaction = acCurDb.TransactionManager.StartTransaction()
Call function1()
dim obj as DbObject = function2()
If TypeOf (obj) Is BlockReference Then
Call function3(obj)
end if
' Save the changes
acTrans.Commit()
End Using ' Transaction
End Using ' Lock
End Sub
Function method1()
' Get the current document and database
Dim acDoc As Document = Application.DocumentManager.MdiActiveDocument
Dim acCurDb As Database = acDoc.Database
Dim acTrans As Transaction = db.TransactionManager.TopTransaction
Dim blkTable As BlockTable = acTrans.GetObject(acCurDb.BlockTableId, OpenMode.ForRead)
If blkTable.Has(_blockName) Then
......
End If
End Function
Function method2() as DBObject
' Get the current document and database
Dim acDoc As Document = Application.DocumentManager.MdiActiveDocument
Dim acCurDb As Database = acDoc.Database
Dim acTrans As Transaction = db.TransactionManager.TopTransaction
Dim pPtOpts As PromptEntityOptions = New PromptEntityOptions("")
' Prompt for the start point
pPtOpts.Message = vbLf & _prompt
pPtOpts.AllowNone = False
Dim pPtRes As PromptEntityResult = acDoc.Editor.GetEntity(pPtOpts)
' Exit if the user presses ESC or cancels the command
If pPtRes.Status = PromptStatus.Cancel Then Return Nothing
Return acTrans.GetObject(pPtRes.ObjectId, OpenMode.ForRead)
End Function
sub method3(_blkRef as BlockReference)
' Get the current document and database
Dim acDoc As Document = Application.DocumentManager.MdiActiveDocument
Dim acCurDb As Database = acDoc.Database
Dim acTrans As Transaction = db.TransactionManager.TopTransaction
Dim attCol As AttributeCollection = _blkRef.AttributeCollection
For Each attId As ObjectId In attCol
Dim attRef As AttributeReference = DirectCast(acTrans.GetObject(attId, OpenMode.ForWrite), AttributeReference)
Dim recievedValue As String = get_AttVal(attRef.Tag, _attVals)
If Not String.IsNullOrEmpty(recievedValue) Then attRef.TextString = recievedValue
Next
End sub
Currently I'm having troubles with quite the opposite of managing numerous transactions, managing a single transaction :
I'm dodging the following error left & right :
"Operation is not valid due to the current state of the object."
I've got code wanting to open ForRead on an object that apperently allready got opened ForRead/Write earlier in the same Transaction.
Maybe i'm making this way to complex ?
-
By vertical I mean
Civil3D, Autocad Arch, AutoCAD MEP, etc.... A product that adds to or extends AutoCAD.
Got to get a job out but will post something later or someone else will post something better.
-
Nope, i'm writing .NET in a 100% pure AutoCAD backdrop.
Replacing 400 pages of VBA-code I wrote over the past 5 years for the company I work for. (don't ask :-D)
-
This is really difficult to try to walk you through with only having a small piece of the whole problem. But to fix your invalid state problem I'm guessing you've opened the attributes and blockreference for write and you're having issues opening them later. A downgrade open will solve this problem for you I do believe. If you wish to minimize the overall changes to your code instead of using the top transaction within each subroutine create a new transaction, I think (you will need to confirm) that when the nested transaction is ended the open state of the objects is downgraded for you so you won't need to modify code much.
Transactions can be nested—that is, you can start one transaction inside another and end or abort the recent transaction. The transaction manager maintains transactions in a stack, with the most recent transaction at the top of the stack. When you start a new transaction using AcTransactionManager::startTransaction(), the new transaction is added to the top of the stack and a pointer to it is returned (an instance of AcTransaction). When someone calls AcTransactionManager::endTransaction() or AcTransactionManager::abortTransaction(), the transaction at the top of the stack is ended or aborted.
When object pointers are obtained from object IDs, they are always associated with the most recent transaction. You can obtain the recent transaction using AcTransactionManager::topTransaction(), then use AcTransaction::getObject() or AcTransactionManager::getObject() to obtain a pointer to an object. The transaction manager automatically associates the object pointers obtained with the recent transaction. You can use AcTransaction::getObject() only with the most recent transaction.
When nested transactions are started, the object pointers obtained in the outer containing transactions are also available for operation in the innermost transaction. If the recent transaction is aborted, all the operations done on all the objects (associated with either this transaction or the containing ones) since the beginning of the recent transaction are canceled and the objects are rolled back to the state at the beginning of the recent transaction. The object pointers obtained in the recent transaction cease to be valid once it's aborted.
If the innermost transaction is ended successfully by calling AcTransactionManager::endTransaction(), the objects whose pointers were obtained in this transaction become associated with the containing transaction and are available for operation. This process is continued until the outermost (first) transaction is ended, at which time modifications on all the objects are committed. If the outermost transaction is aborted, all the operations on all the objects are canceled and nothing is committed.
-
Hello,
I don't know, wether the OP's issiue is solved or not, I just liked to place a hint for getting Blockdef-Names (Blocktablerecords)
Blockreferences have 3 Id-s to their Blocktablerecords:
- .BlockTablerecord: points always to the deifning Block, meaning to the "real" one in case of a static Block, and to the Anonymous one (*U....) in case of (modified) dynamic Block
- .AnonymousBlocktableRecord: points to the defining Blockdefinition (*U...) of an altered dynamic Block (means, some Properties were set), else null/Nothing
- .DynamicBlocktableRecord: points to the "main" BTR, so to the real one in case of static block, and to the master in case of dynamic blocks, this is ALWAYS set, regardless of Blocktype
Conclusion:
to test the Name, or content of ANY Blockref, you can always parse the "br.DynamicBlockTableRecord"
As I said, this may not help in this issue, but could be useful if someone reads the post.
BR,
Daniel