Author Topic: .IsDynamicBlock returns false ?  (Read 6739 times)

0 Members and 1 Guest are viewing this topic.

Bert

  • Guest
.IsDynamicBlock returns false ?
« 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 :

Code: [Select]
   
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 ?





n.yuan

  • Bull Frog
  • Posts: 348
Re: .IsDynamicBlock returns false ?
« Reply #1 on: September 03, 2013, 09:42:27 AM »
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.

Bert

  • Guest
Re: .IsDynamicBlock returns false ?
« Reply #2 on: September 03, 2013, 10:20:56 AM »
Hey n.yuan, thanks for replying !

This is how i'm doing this (code-wise):

Step 1) Inserting my Dynamic Blocks :
Code: [Select]
        ...
        ' 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
Code: [Select]
    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
Code: [Select]
   
    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
Code: [Select]
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
Code: [Select]

    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

Code: [Select]
 
       '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 :

Code: [Select]
  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 :

Code: [Select]
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 ?
« Last Edit: September 03, 2013, 10:26:06 AM by Bert »

WILL HATCH

  • Bull Frog
  • Posts: 450
Re: .IsDynamicBlock returns false ?
« Reply #3 on: September 03, 2013, 03:17:40 PM »
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?

n.yuan

  • Bull Frog
  • Posts: 348
Re: .IsDynamicBlock returns false ?
« Reply #4 on: September 03, 2013, 03:39:22 PM »
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

Bert

  • Guest
Re: .IsDynamicBlock returns false ?
« Reply #5 on: September 03, 2013, 04:39:10 PM »
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 !



WILL HATCH

  • Bull Frog
  • Posts: 450
Re: .IsDynamicBlock returns false ?
« Reply #6 on: September 03, 2013, 05:26:45 PM »
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

Jeff H

  • Needs a day job
  • Posts: 6144
Re: .IsDynamicBlock returns false ?
« Reply #7 on: September 03, 2013, 06:55:51 PM »
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.
 
 
 
 
 
 
 

mohnston

  • Bull Frog
  • Posts: 305
  • CAD Programmer
Re: .IsDynamicBlock returns false ?
« Reply #8 on: September 04, 2013, 12:49:30 AM »
What happens if you pass the blockReference byRef instead of byVal?
It's amazing what you can do when you don't know what you can't do.
CAD Programming Solutions

Bert

  • Guest
Re: .IsDynamicBlock returns false ?
« Reply #9 on: September 04, 2013, 02:16:27 AM »
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


Bert

  • Guest
Re: .IsDynamicBlock returns false ?
« Reply #10 on: September 04, 2013, 02:24:51 AM »
I assume that following code-example works identically as what WILL HATCH suggests ?
(It involves passing the 'active' transaction trough an argument)

Code: [Select]
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
« Last Edit: September 04, 2013, 04:08:05 AM by Bert »

Jeff H

  • Needs a day job
  • Posts: 6144
Re: .IsDynamicBlock returns false ?
« Reply #11 on: September 04, 2013, 03:45:10 AM »
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.
 
 

Bert

  • Guest
Re: .IsDynamicBlock returns false ?
« Reply #12 on: September 04, 2013, 09:44:11 AM »
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 :

Code: [Select]
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 ?


Jeff H

  • Needs a day job
  • Posts: 6144
Re: .IsDynamicBlock returns false ?
« Reply #13 on: September 04, 2013, 10:25:12 AM »
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.
 

Bert

  • Guest
Re: .IsDynamicBlock returns false ?
« Reply #14 on: September 04, 2013, 10:35:58 AM »
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)

WILL HATCH

  • Bull Frog
  • Posts: 450
Re: .IsDynamicBlock returns false ?
« Reply #15 on: September 04, 2013, 02:49:55 PM »
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.

Quote from: docs
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.

dba

  • Guest
Re: .IsDynamicBlock returns false ?
« Reply #16 on: November 02, 2015, 06:45:25 AM »
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