Author Topic: How to get Entities in a Block.  (Read 18517 times)

0 Members and 1 Guest are viewing this topic.

tik

  • Guest
How to get Entities in a Block.
« on: January 11, 2012, 11:23:45 AM »
This should be very straight forward but I am having hard time to get it to work. I  wrote the following sample code but I am having issues, I am hoping there is a easier way to do than my way.

In my code I explode the block reference for the block, I can see all the entities in the collection but entities doesn't have id's in them or XData for each entity.

I really appreciate if someone can point me in the right direction.

Thanks in advance.

<Code>
public GetEntities( string ThisBlock)
{
           ObjectId blockId =  GetObjectId(ThisBlock);

           DBObject[] ThisBlockEntities = GetBlockEntities(blockId);

            DBObjectCollection entitycoll = new DBObjectCollection();

            BlockReference blkRef = ThisBlockEntities[0] as BlockReference;
            if (blkRef != null)
            {
                blkRef.Explode(entitycoll);
            }

            for (ListCount = 0; ListCount < entitycoll.Count; ListCount++)
            {
                 // entitycoll has all the entities but they dont have ids in them.             
            }
}

//This method return the objectid for the block based on the name.
public ObjectId GetObjectID(string name)
        {
            // Get the current document and database, and start a transaction
            Document acDoc = Application.DocumentManager.MdiActiveDocument;
            Database acCurDb = acDoc.Database;
            ObjectId id = new ObjectId();
            // Starts a new transaction with the Transaction Manager
            using (Transaction acTrans = acCurDb.TransactionManager.StartTransaction())
            {
                // Open the Block table record for read
                BlockTable acBlkTbl;
                BlockTableRecord acBlkTblRec;
                acBlkTbl = acTrans.GetObject(acCurDb.BlockTableId, OpenMode.ForRead, false) as BlockTable;
                if (acBlkTbl != null)
                {
                    if (acBlkTbl.Has(name))
                    {
                        acBlkTblRec = acTrans.GetObject(acBlkTbl[BlockTableRecord.ModelSpace], OpenMode.ForRead) as BlockTableRecord;
                        if (acBlkTblRec != null)
                        {
                            id = acBlkTblRec.Id;
                        }
                    }
                }
                acTrans.Commit();
            }
            return id;
        }

// This method is returning a block reference
public DBObject[] GetBlockEntities(ObjectId blockId)
        {
            // Get the current document and database, and start a transaction
            Document acDoc = Application.DocumentManager.MdiActiveDocument;
            Database acCurDb = acDoc.Database;
            DBObject[] dbObjectCollection = null;
            DBObjectCollection coll = new DBObjectCollection();
            using (Transaction acTrans = acCurDb.TransactionManager.StartTransaction())
            {
                // Open the Block table record Model space for read
                BlockTableRecord acBlkTblRec;
                acBlkTblRec = acTrans.GetObject(blockId, OpenMode.ForRead) as BlockTableRecord;
                int nCnt = 0;
                //acDoc.Editor.WriteMessage("\nModel space objects: ");
                // Step through each object in Model space and// display the type of object found
                foreach (ObjectId acObjId in acBlkTblRec)
                {
                    coll.Add(acTrans.GetObject(acObjId, OpenMode.ForRead) as DBObject);
                }
                dbObjectCollection = new DBObject[coll.Count];
                coll.CopyTo(dbObjectCollection, 0);
            }
            return dbObjectCollection;
        }
</Code>

kaefer

  • Guest
Re: How to get Entities in a Block.
« Reply #1 on: January 11, 2012, 12:38:39 PM »
In my code I explode the block reference for the block, I can see all the entities in the collection but entities doesn't have id's in them

They are clones of those contained in the BlockTableRecord and haven't been added to the database. That's why there's no valid ObjectId.

If you create new non-database-resident objects, you are responsible to Dispose() them. Alternatively, you could wrap the DBObjectCollection itself in a using statement, which would take care of disposing the objects contained when leaving its scope.

tik

  • Guest
Re: How to get Entities in a Block.
« Reply #2 on: January 11, 2012, 12:55:59 PM »
Kaefer,

Thanks for the response,I will add a using around my collection. So is there some way to get actual entities instead of the clones.

regards
tik.

Jeff H

  • Needs a day job
  • Posts: 6150
Re: How to get Entities in a Block.
« Reply #3 on: January 11, 2012, 01:20:10 PM »
Have you tried looking at the entites in the BlockTableRecord(Block Definition) of the BlockReference?

tik

  • Guest
Re: How to get Entities in a Block.
« Reply #4 on: January 11, 2012, 03:45:03 PM »
Jeff,
Thanks for the reply, how can I get the entities from the BlockTableRecord, do I need to explode BlockTableRecord to get the entities ?

tik.

Jeff H

  • Needs a day job
  • Posts: 6150
Re: How to get Entities in a Block.
« Reply #5 on: January 11, 2012, 04:41:19 PM »
You can use BlockReference.BlockTableRecord Property
For dynamics blocks you might want the dynamic block def  etc.........
 
Are we using the same terminology?
It might make it easier to never say the word block unless followed by 'Definition', 'Reference', 'TableRecord', etc....
 
Code: [Select]
[CommandMethod("BlockDefinition")]
        public void BlockDefinition()
        {
            Document doc = Application.DocumentManager.MdiActiveDocument;
            Database db = doc.Database;
            Editor ed = doc.Editor;

            using (Transaction trx = db.TransactionManager.StartTransaction())
            {
                PromptEntityOptions peo = new PromptEntityOptions("\nSelect Block");
                peo.SetRejectMessage("Not a Block");
                peo.AddAllowedClass(typeof(BlockReference), true);
                PromptEntityResult per = ed.GetEntity(peo);
                if (per.Status != PromptStatus.OK)
                {
                    return;
                }
                BlockReference bref = trx.GetObject(per.ObjectId, OpenMode.ForRead) as BlockReference;
                BlockTableRecord btr = (BlockTableRecord)trx.GetObject(bref.BlockTableRecord, OpenMode.ForRead);
                foreach (ObjectId id in btr)
                {
                    ed.WriteMessage("{0} - {1}\n", id.ObjectClass.DxfName, id.ToString());
               
                }

                trx.Commit();
            }
        }
 

Code prints for a simple BlockReference that references a BlockTableRecord with a Arc, circle, line
Quote
CIRCLE - (8796082561152)
LINE - (8796082561168)
ARC - (8796082561184)
« Last Edit: January 11, 2012, 04:45:53 PM by Jeff H »

TheMaster

  • Guest
Re: How to get Entities in a Block.
« Reply #6 on: January 11, 2012, 10:13:09 PM »

If you create new non-database-resident objects, you are responsible to Dispose() them. Alternatively, you could wrap the DBObjectCollection itself in a using statement, which would take care of disposing the objects contained when leaving its scope.


Unless it has changed recently, I don't believe DBObjectCollection
disposes its elements when it gets disposed.


kaefer

  • Guest
Re: How to get Entities in a Block.
« Reply #7 on: January 12, 2012, 02:56:03 AM »
Unless it has changed recently, I don't believe DBObjectCollection disposes its elements when it gets disposed.

We've been at it before, inconclusively. Even then, under DBObjectCollection Class, arxmgd.chm says:
Quote
When a DBObjectCollection is disposed, it looks at the set of unmanaged objects it holds, and the set of wrappers it has generated. For those objects with wrappers, it assumes the wrappers will manage the lifetime of the object, and it does nothing. For those objects without wrappers, it will delete them or close them: it takes responsibility for object lifetime.
...
it is important to be sure DBObjectCollections are properly (perhaps explicitly) disposed.

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8702
  • AKA Daniel
Re: How to get Entities in a Block.
« Reply #8 on: January 12, 2012, 03:16:59 AM »
Maybe as a test, you can copy the dbobjects to another container such as list, call dispose the DBObjectCollection,  then call  IsDisposed on one of the items... just saying

TheMaster

  • Guest
Re: How to get Entities in a Block.
« Reply #9 on: January 12, 2012, 07:11:03 AM »

We've been at it before, inconclusively. Even then, under DBObjectCollection Class, arxmgd.chm says:

When a DBObjectCollection is disposed, it looks at the set of unmanaged objects it holds, and the set of wrappers it has generated. For those objects with wrappers, it assumes the wrappers will manage the lifetime of the object, and it does nothing. For those objects without wrappers, it will delete them or close them: it takes responsibility for object lifetime.
...

Regarding:

Quote
"Alternatively, you could wrap the DBObjectCollection itself in a using statement, which would take care of disposing the objects contained when leaving its scope."

I took the word "Dispose" to imply managed wrappers rather than pointers to native AcDbObjects, and DBObjectCollection (as the docs imply) doesn't call Dispose() on managed wrappers or delete native AcDbObjects for which managed wrappers were created.

For the items in a DBObjectCollection for which managed wrappers were created (which happens the first time an item is accessed via the indexer or the enumerator), the managed wrappers for those items must be treated the same way that all managed wrappers for AcDbObjects that are not database-resident are treated (e.g., it is absolutely mandatory that their Dispose() method be called to prevent them from being collected by the GC).

Quote
it is important to be sure DBObjectCollections are properly (perhaps explicitly) disposed.

Yes, because if you don't call Dispose() on a DBObjectCollection, the destructors of the native AcDbObjects it contains that are not database-resident will be called on the thread on which the garbage collector runs, where AutoCAD API access is limited or dangerous at best, and that can easily lead to a fatal error.

All of this absurdity is largely due to the quirk that results from the fact that the .NET garbage collector runs on a non-AutoCAD thread where AutoCAD API access is not fully-supported. Because the destructors of some native objects can make calls to AutoCAD APIs that are not thread-safe, the managed wrappers for those objects must always be disposed (from an AutoCAD thread, where most managed user code runs), to ensure the destructors of the wrapped native objects execute on that same AutoCAD thread.


« Last Edit: January 12, 2012, 07:26:37 AM by TheMaster »

kaefer

  • Guest
Re: How to get Entities in a Block.
« Reply #10 on: January 12, 2012, 08:16:10 AM »
Maybe as a test, you can copy the dbobjects to another container such as list, call dispose the DBObjectCollection,  then call  IsDisposed on one of the items... just saying

Just tried:
Quote
Select Entity:
28 Objects in collection
28 Objects in list
  0: IsDisposed False
  1: IsDisposed False
  etc.

... Because the destructors of some native objects can make calls to AutoCAD APIs that are not thread-safe, the managed wrappers for those objects must always be disposed (from an AutoCAD thread, where most managed user code runs), to ensure the destructors of the wrapped native objects execute on that same AutoCAD thread.

Ok, I was wrong. We do have to dispose the objects (that aren't added to the Database) and also the DBObjectCollection.

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8702
  • AKA Daniel
Re: How to get Entities in a Block.
« Reply #11 on: January 12, 2012, 08:18:58 AM »
Threading has nothing to do with it. the issue is 'when' to destroy a native pointer within a wrapper, in a garbage collected framework that does not have scoping? A, you don't, you suppress the finalizer and hand it off to the developer then put words like " it's important to dispose the object (perhaps explicitly)" in your documentation.  but it is what it is, if you create it and don't hand it off to an owner, dispose it, Easy Peasy Lemon Squeezy

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8702
  • AKA Daniel
Re: How to get Entities in a Block.
« Reply #12 on: January 12, 2012, 08:21:30 AM »
...We do have to dispose the objects (that aren't added to the Database) and also the DBObjectCollection.

Awesome, good to know  8-)

TheMaster

  • Guest
Re: How to get Entities in a Block.
« Reply #13 on: January 12, 2012, 08:23:56 AM »
Maybe as a test, you can copy the dbobjects to another container such as list, call dispose the DBObjectCollection,  then call  IsDisposed on one of the items... just saying

Watching AutoCAD unceremoniously die on the next GC after my code failed to call Dispose() on the DBObjects that I didn't add to a database was a pretty good test for me :laugh:

And there was that bug in the DBObjectCollection that only made matters worse (it didn't create managed wrappers when you tried to get them from the indexer, so you had to first use foreach() to iterate over the entire collection before you could use the indexer).  :roll:

A few years back I made wrapper class for DBObjectCollection that made sure that any DBObjects that were returned were properly disposed when the wrapper and wrapped DBObjectCollection were disposed. 

I routinely use that wrapper nowadays to simplify things.



TheMaster

  • Guest
Re: How to get Entities in a Block.
« Reply #14 on: January 12, 2012, 08:36:15 AM »
Threading has nothing to do with it. the issue is 'when' to destroy a native pointer within a wrapper, in a garbage collected framework that does not have scoping? A, you don't, you suppress the finalizer and hand it off to the developer then put words like " it's important to dispose the object (perhaps explicitly)" in your documentation.  but it is what it is, if you create it and don't hand it off to an owner, dispose it, Easy Peasy Lemon Squeezy

Well, threading has everything to do with why you must determinstically call Dispose() on DBObjects that are not database-resident.

But, in a managed environment with garbage collection, it shouldn't be necessary to call Dispose() on anything, unless there is something special that must occur when the object is no longer used (an example would be a class that manages an open file, where calling Dispose() would cause the file to be closed, allowing others to access it).

Unfortunately, we're stuck with a cooperative fiber-mode application that has many APIs that aren't thread-safe, which means they can only be called on one of the application-managed threads/fibers, and that is the root cause of the utterly-ridiculous Dispose() requirement that vastly-complicates development of managed AutoCAD solutions.