.NET
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Appending attributes to a BlockReference

16 REPLIES 16
SOLVED
Reply
Message 1 of 17
Ed.Jobe
1516 Views, 16 Replies

Appending attributes to a BlockReference

I have this extension method that adds attributes to a BlockReference. Its's a variation of Giles' extension posted here.


        /// <summary>
        /// Extension method that adds <see cref="AttributeReference"/>'s to a <see cref="BlockReference"/>.
        /// AttributeReference properties are taken from the block being inserted.
        /// </summary>
        /// <returns>Void.</returns>
        public static void AddAttributeReferences(this BlockReference target)
        {
            if (target == null)
                throw new ArgumentNullException("target");

            Transaction tr = target.Database.TransactionManager.TopTransaction;
            if (tr == null)
                throw new AcRx.Exception(ErrorStatus.NoActiveTransactions);

            BlockTableRecord btr = (BlockTableRecord)tr.GetObject(target.BlockTableRecord, OpenMode.ForRead);

            if (btr.HasAttributeDefinitions)
            {
                foreach (AttributeDefinition attDef in btr.AttributeDefinitions())
                {
                    if (!attDef.Constant)
                    {
                        using (AttributeReference attRef = new AttributeReference())
                        {
                            attRef.SetAttributeFromBlock(attDef, target.BlockTransform);
                            attRef.Position = attDef.Position.TransformBy(target.BlockTransform);
                            attRef.TextString = attDef.TextString;
                            target.AttributeCollection.AppendAttribute(attRef);
                            tr.AddNewlyCreatedDBObject(attRef, true);
                        }
                    }
                }
            }
        }

 

It is being called by a sub that inserts a block. If I comment out the line to add the attributes, the block inserts fine. If I try to run AppendAttributeReferences(), it runs without error, but then I get this unhandled error in acad.exe when the calling sub trys to commit the transaction. Here is the stack trace.

 

System.InvalidOperationException: Operation is not valid due to the current state of the object.
at Autodesk.AutoCAD.DatabaseServices.Transaction.CheckTopTransaction()
at Autodesk.AutoCAD.DatabaseServices.Transaction.DeleteUnmanagedObject()
at Autodesk.AutoCAD.Runtime.DisposableWrapper.!DisposableWrapper()
at Autodesk.AutoCAD.Runtime.DisposableWrapper.Dispose(Boolean )
at Autodesk.AutoCAD.Runtime.DisposableWrapper.Dispose()
at TID_.TID_Commands.TitleBlockInsert()
at Autodesk.AutoCAD.Runtime.CommandClass.InvokeWorker(MethodInfo mi, Object commandObject, Boolean bLispFunction)
at Autodesk.AutoCAD.Runtime.CommandClass.InvokeWorkerWithExceptionFilter(MethodInfo mi, Object commandObject, Boolean bLispFunction)
at Autodesk.AutoCAD.Runtime.PerDocumentCommandClass.Invoke(MethodInfo mi, Boolean bLispFunction)
at Autodesk.AutoCAD.Runtime.CommandClass.CommandThunk.Invoke()

Ed


Did you find this post helpful? Feel free to Like this post.
Did your question get successfully answered? Then click on the ACCEPT SOLUTION button.
How to post your code.

EESignature

16 REPLIES 16
Message 2 of 17
SENL1362
in reply to: Ed.Jobe

I'll use an equally designed function without any problems, so the problem may be caused in previous actions.
For instance how is the Transaction started, StartTransaction/StartOpenClose...
What happens when you start a Transaction, insert the BlockRef, add the Attributes and then Commit.


Message 3 of 17
Ed.Jobe
in reply to: SENL1362

If I comment out the call to this method, the block inserts fine. Its only when I try to add the atts that I get the error. I thought it may be due to the state of the BlockReference being added to so I stuck in an UpgradeOpen prior to calling AppendAttributeReferences, but that didn't help either. The calling method is itself an extension method, but the transaction was started with StartTransaction. I also tried replacing the call to the TopTransaction with a new one. This didn't help either. The atts get added to the blockref, but then I can't finish adding the blockref. I'm still trying to trace down what transaction is being used.

Ed


Did you find this post helpful? Feel free to Like this post.
Did your question get successfully answered? Then click on the ACCEPT SOLUTION button.
How to post your code.

EESignature

Message 4 of 17
Keith.Brown
in reply to: Ed.Jobe

It looks like there is no active transaction when you run the extension method.  I would check to make sure that you have not already commited the transaction as the previous poster already mentioned.

Message 5 of 17
Ed.Jobe
in reply to: Keith.Brown

Keith, I was able to find out that there are 2 transactions going at the time. AppendAttributeReferences is able to get the top transaction and complete. In debug mode, the debugger doesn't show very much info on transactions. I don't know which transaction is which or whether its closed or open. I only know that there are 2. Still working on this to see what else I can find out. Thanks.

Ed


Did you find this post helpful? Feel free to Like this post.
Did your question get successfully answered? Then click on the ACCEPT SOLUTION button.
How to post your code.

EESignature

Message 6 of 17
Ed.Jobe
in reply to: Ed.Jobe

Is it possible to have 2 sequential transactions in the same command? i.e. the first executes and is committed then the second is started and then committed? I know you can have nested transactions. Due to some helper delegate functions I wrote for handling transactions, what is happening is that in the first transaction, the block gets imported from a file and then in the second transaction, the method to add the AttributeReferences nees to modify the BlockReference just created.

Ed


Did you find this post helpful? Feel free to Like this post.
Did your question get successfully answered? Then click on the ACCEPT SOLUTION button.
How to post your code.

EESignature

Message 7 of 17
Ed.Jobe
in reply to: Ed.Jobe

I've refactored the calling method so that everything happens in one transaction, but I still get the same error on the line to commit the transaction. Here is the calling method. UsingCurrentSpace is a delegate that starts/ends a transaction and passes a BlockTableRecord of the current space. If I comment out the line to add the atts, this runs fine. The error message refers to the state of an object but doesn't say what object.

        public static BlockReference InsertFileAsBlock(string sourceFilePath, BlockInfo BI, string BlockName)
        {
           try
           {
              ObjectId btrId = ImportFileAsBlock(sourceFilePath, BlockName);
              BlockReference blkr = new BlockReference(BI.Position, btrId);
              blkr.Rotation = BI.Rotation;
              blkr.ScaleFactors = BI.ScaleFactors; 
              Active.Database.UsingCurrentSpace(
                 (Transaction tr, BlockTableRecord btr) =>
                    {
                           btr.AppendEntity(blkr);
                           tr.AddNewlyCreatedDBObject(blkr, true);
                           blkr.AddAttributeReferences(tr);
                    }
                 );
              return blkr;                    
           }
           catch 
           {
              throw;
           }

Ed


Did you find this post helpful? Feel free to Like this post.
Did your question get successfully answered? Then click on the ACCEPT SOLUTION button.
How to post your code.

EESignature

Message 8 of 17
SENL1362
in reply to: Ed.Jobe

So the UsingCurrentSpace create and return the Transaction?
I don't see the ByRef argument, so the Transaction might not be returned.

Message 9 of 17
_gile
in reply to: Ed.Jobe

Hi,

 

Did you try in a more classical way, without your transaction helper.

By my side, I use a quite large library of Helpers (mainly extension methods) but none to wrap the Open / Dispose transaction mechanism, I'd rather explicitly write it.

 

public static ObjectId InsertFileAsBlock(string sourceFilePath, BlockInfo BI, string BlockName)
{
    ObjectId brId = ObjectId.Null;
    ObjectId btrId = ImportFileAsBlock(sourceFilePath, BlockName);
    if (btrId != ObjectId.Null)
    {
        using (Transaction tr = btrId.Database.TransactionManager.StartTransaction())
        {
            blkr = new BlockReference(BI.Position, btrId)
            { Rotation = BI.Rotation, ScaleFactors = BI.ScaleFactors }
            brId = btr.AppendEntity(blkr);
            tr.AddNewlyCreatedDBObject(blkr, true);
            blkr.AddAttributeReferences(tr);
            tr.Commit()
            return blkr;
        }
    }
    return brId;
}

One other thing, It's unsafe to return a DBObject which have been disposed: in your code, blkr is disposed when the transaction it has been added to is diposed, so your InsertFileAsBlock function returns a disposed object.

Return an ObjectId instead, as shown upper, or rewrite the function so that it uses a top transaction from where it is called.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 10 of 17
Ed.Jobe
in reply to: _gile


@_gile wrote:

Did you try in a more classical way, without your transaction helper.


 Yes, but I still had the error so I put it back.


@_gile wrote:

One other thing, It's unsafe to return a DBObject which have been disposed: in your code, blkr is disposed when the transaction it has been added to is diposed, so your InsertFileAsBlock function returns a disposed object.

Return an ObjectId instead, as shown upper, or rewrite the function so that it uses a top transaction from where it is called.


 Thanks for catching that. As part of this troubleshooting, I tried something and forgot to change it back.

 

I used your last code. I had to clean it up a little and add a line to get the CurrentSpace. But I still get the error. Here is what I have now.

 

        public static ObjectId InsertFileAsBlock(string sourceFilePath, BlockInfo BI, string BlockName)
        {            
            ObjectId NewBlkRefId = ObjectId.Null;
            ObjectId NewBlkId = ImportFileAsBlock(sourceFilePath, BlockName);
            if (NewBlkId != ObjectId.Null)
            {
                using (Transaction tr = NewBlkId.Database.TransactionManager.StartTransaction())
                {
                    BlockTableRecord btrCurrentSpace = (BlockTableRecord)tr.GetObject(NewBlkId.Database.CurrentSpaceId, OpenMode.ForWrite);
                    BlockReference brNewBlock = new BlockReference(BI.Position, NewBlkId)
                       {Rotation = BI.Rotation, ScaleFactors = BI.ScaleFactors };
                    NewBlkRefId = btrCurrentSpace.AppendEntity(brNewBlock);
                    tr.AddNewlyCreatedDBObject(brNewBlock, true);
                    brNewBlock.AddAttributeReferences(tr);
                    tr.Commit();
                }
            }
            return NewBlkRefId;
        }

Again, this works fine if I comment out the line to add the atts. The block gets inserted, but without atts. Thanks for sticking with this problem.

 

Ed


Did you find this post helpful? Feel free to Like this post.
Did your question get successfully answered? Then click on the ACCEPT SOLUTION button.
How to post your code.

EESignature

Message 11 of 17
Ed.Jobe
in reply to: SENL1362


@SENL1362 wrote:
So the UsingCurrentSpace create and return the Transaction?
I don't see the ByRef argument, so the Transaction might not be returned.


Its not really "returning" a transaction. Its working in the context of a delegate. The transaction isn't the problem, the block get inserted. The problem comes when you commit the transaction. There is some object that is not in a proper state when acad.exe does CheckTopTransaction().  That's why I was asking if you could have sequential transactions. I don't recall having seen it being done.

Ed


Did you find this post helpful? Feel free to Like this post.
Did your question get successfully answered? Then click on the ACCEPT SOLUTION button.
How to post your code.

EESignature

Message 12 of 17
_gile
in reply to: Ed.Jobe

The code  I posted here works for me and for Keith to who accepted it as solution. SENL1362 said he uses a similar code working for him too. So I think the problem is due to some modification you made the this code.

Did you try with the AddAttributeReferences() extension method as is in the related post ? You an call it passing null for the attValues argument if you do not want to set attributes values.

I can see you have an AttributeDefinitions() one, you don't show the code.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 13 of 17
_gile
in reply to: _gile

Here's an example of how I'd do it using some extension methods from my library.
Notice that all used extension methods need to be called from within a transaction and they are called from the same transaction opened in the main method.

 

The 'main' function

        public static ObjectId InsertFileAsBlock(string sourceFilePath, Point3d position, double rotation, Scale3d scale, string blockName)
        {
            Database db = HostApplicationServices.WorkingDatabase;
            ObjectId brId;
            using (Transaction tr = db.TransactionManager.StartTransaction())
            {
                BlockTable bt = db.BlockTableId.GetObject<BlockTable>();
                ObjectId btrId = bt.GetBlock(blockName, sourceFilePath);
                if (btrId == ObjectId.Null)
                    return ObjectId.Null;
                BlockReference br = new BlockReference(position, btrId) { Rotation = rotation, ScaleFactors = scale };
                BlockTableRecord curSpace = db.CurrentSpaceId.GetObject<BlockTableRecord>(OpenMode.ForWrite);
                brId = curSpace.Add(br);
                br.AddAttributeReferences();
                tr.Commit();
            }
            return brId;
        }

Extension methods

    public static class ExtensionMethods
    {
        // Opens the object of the specified type
        public static T GetObject<T>(
            this ObjectId id,
            OpenMode mode = OpenMode.ForRead,
            bool openErased = false,
            bool forceOpenOnLockedLayer = false)
            where T : DBObject
        {
            if (id == ObjectId.Null)
                throw new ArgumentNullException("id");

            Transaction tr = id.Database.TransactionManager.TopTransaction;
            if (tr == null)
                throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.NoActiveTransactions);

            return (T)tr.GetObject(id, mode, openErased, forceOpenOnLockedLayer);
        }

        // Opens the entities of the specified type contained in the block table record
        public static IEnumerable<T> GetObjects<T>(
          this BlockTableRecord btr,
          OpenMode mode = OpenMode.ForRead,
          bool openErased = false,
          bool forceOpenOnLockedLayers = false) where T : Entity
        {
            if (btr == null)
                throw new ArgumentNullException("btr");

            Transaction tr = btr.Database.TransactionManager.TopTransaction;
            if (tr == null)
                throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.NoActiveTransactions);

            BlockTableRecord source = openErased ? btr.IncludingErased : btr;
            RXClass rxClass = RXClass.GetClass(typeof(T));
            foreach (ObjectId id in source)
            {
                if (id.ObjectClass == rxClass || id.ObjectClass.IsDerivedFrom(rxClass))
                {
                    yield return (T)tr.GetObject(id, mode, openErased, forceOpenOnLockedLayers);
                }
            }
        }

        // Adds an entity to the block table record
        public static ObjectId Add(this BlockTableRecord owner, Entity ent)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");
            if (ent == null)
                throw new ArgumentNullException("entity");
            Transaction trans = owner.Database.TransactionManager.TopTransaction;
            if (trans == null)
                throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.NotTopTransaction);
            try
            {
                ObjectId id = owner.AppendEntity(ent);
                trans.AddNewlyCreatedDBObject(ent, true);
                return id;
            }
            catch
            {
                ent.Dispose();
                throw;
            }
        }

        // Gets the block table record ObjectId from its blockname if already contained by the block table
        // or imports a dwg file.
        public static ObjectId GetBlock(this BlockTable blockTable, string blockName, string fileName = null)
        {
            if (blockTable == null)
                throw new ArgumentNullException("blockTable");

            Database db = blockTable.Database;
            if (blockTable.Has(blockName))
                return blockTable[blockName];

            try
            {
                if (string.IsNullOrEmpty(fileName))
                    fileName = blockName + ".dwg";
                string blockPath;
                if (File.Exists(fileName))
                    blockPath = fileName;
                else
                    blockPath = HostApplicationServices.Current.FindFile(fileName, db, FindFileHint.Default);

                blockTable.UpgradeOpen();
                using (Database tmpDb = new Database(false, true))
                {
                    tmpDb.ReadDwgFile(blockPath, FileShare.Read, true, null);
                    return blockTable.Database.Insert(blockName, tmpDb, true);
                }
            }
            catch
            {
                return ObjectId.Null;
            }
        }

        // Adds the attribute references to the block references and sets the values contained in the dictionary
        public static DBObjectCollection AddAttributeReferences(this BlockReference target, Dictionary<string, string> attValues = null)
        {
            if (target == null)
                throw new ArgumentNullException("target");

            Transaction tr = target.Database.TransactionManager.TopTransaction;
            if (tr == null)
                throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.NoActiveTransactions);

            BlockTableRecord btr = target.BlockTableRecord.GetObject<BlockTableRecord>();
            DBObjectCollection retVal = new DBObjectCollection();

            foreach (AttributeDefinition attDef in btr.GetObjects<AttributeDefinition>())
            {
                AttributeReference attRef = new AttributeReference();
                attRef.SetAttributeFromBlock(attDef, target.BlockTransform);
                if (attValues != null && attValues.ContainsKey(attDef.Tag.ToUpper()))
                {
                    attRef.TextString = attValues[attDef.Tag.ToUpper()];
                }
                ObjectId id = target.AttributeCollection.AppendAttribute(attRef);
                tr.AddNewlyCreatedDBObject(attRef, true);
                retVal.Add(attRef);
            }
            return retVal;
        }
    }


Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 14 of 17
SENL1362
in reply to: _gile

Nicely coded. 

Message 15 of 17
Ed.Jobe
in reply to: _gile


@_gile wrote:

The code  I posted here works for me and for Keith to who accepted it as solution. SENL1362 said he uses a similar code working for him too. So I think the problem is due to some modification you made the this code.

Did you try with the AddAttributeReferences() extension method as is in the related post ? You an call it passing null for the attValues argument if you do not want to set attributes values.

I can see you have an AttributeDefinitions() one, you don't show the code.


Thanks for taking the time to write some more code. Sorry I didn't get back sooner, I got tied up with something else. The problem was in the AttributeDefinitions() extension. I didn't suspect it because it seemed to work as intended and I was just using it to set properties. It seemed to work because I could inspect the properties during debug. FYI, here is the extension method. I rewrote AddAttributeReferences to filter like you did instead of calling AttributeDefinitions(), but it seems that I should be able to modify it to take a transaction as an argument  or use the TopTransaction. That's something else I learned from your code.

        /// <summary>
        /// Extension method that returns a <see cref="Collection"/> of <see cref="AttributeDefinition"/>'s from a <see cref="BlockTableRecord"/>.
        /// </summary>
        /// <param name="btr">This BlockTableRecord to use.</param>
        /// <returns>A Collection of AttributeDefinitions.</returns>
        public static Collection<AttributeDefinition> AttributeDefinitions(this BlockTableRecord btr)
        {
            Transaction trans = Active.Database.TransactionManager.StartTransaction();
            Collection<AttributeDefinition> atts = new Collection<AttributeDefinition>();

            RXClass theClass = RXObject.GetClass(typeof(AttributeDefinition));

            // Loop through the entities in model space
            foreach (ObjectId objectId in btr.Cast<ObjectId>())
            {
                AttributeDefinition attd = new AttributeDefinition();
                // Look for entities of the correct type
                if (objectId.ObjectClass.IsDerivedFrom(theClass))
                {
                    attd = objectId.OpenAs<AttributeDefinition>(trans);
                    atts.Add(attd);
                }
            }
            return atts;
        }

OpenAs is a delegate that uses the transaction to open the Id in read mode.

Ed


Did you find this post helpful? Feel free to Like this post.
Did your question get successfully answered? Then click on the ACCEPT SOLUTION button.
How to post your code.

EESignature

Message 16 of 17
Keith.Brown
in reply to: Ed.Jobe

Looking at your code you define your transaction inside the extension method but you fail to commit it or to dispose of it before it goes out of scope. Not committing is technically ok as it will clean up after itself but it is faster to commit the transaction. It appears that you are it making any changes so it should be just fine. However you are not creating the transaction with a using statement which will automatically dispose the transaction so you must dispose it manually which you are not doing. Also what will happen if an error occurs? The code will also exit without the transaction being disposed. If you are going to create a transaction without an using statement you should always wrap it in a try/catch/finally block with the dispose statement in the finally block. This will guarentee that dispose is called if an error occurs.
Message 17 of 17
Ed.Jobe
in reply to: Keith.Brown

Thanks Keith. Even though I got it working using Gilles comments. I would like to see if I can get it working using this extension method as well, just for the sake of learning.

Ed


Did you find this post helpful? Feel free to Like this post.
Did your question get successfully answered? Then click on the ACCEPT SOLUTION button.
How to post your code.

EESignature

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Autodesk DevCon in Munich May 28-29th


Autodesk Design & Make Report

”Boost