Announcements
Due to scheduled maintenance, the Autodesk Community will be inaccessible from 10:00PM PDT on Oct 16th for approximately 1 hour. We appreciate your patience during this time.
.NET
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

BlockReferenceIds is sometimes EMPTY?

6 REPLIES 6
SOLVED
Reply
Message 1 of 7
Anonymous
1628 Views, 6 Replies

BlockReferenceIds is sometimes EMPTY?

I feel like this is more of a sanity check question. I slightly struggle to understand a certain aspect of how AutoCAD manages its blocks in a drawing given the problem I am facing.

 

I am trying to find an inserted block in the drawing with a specific name - if I am not very technical, I would refer to that as a Block Reference, where as the Block/Block Record being the definition of the block itself - please correct me if otherwise.

 

I am using the following function to dig into the Database and retrieve the reference I am seeking:

 

        // Searches the drawing for a block with the specified name.
        // Returns either the block, or null - check accordingly.

        private BlockReference GetBorderReference(string _BorderName)
        {
            BlockReference blkRef = null;

            using (Transaction _trans = acDb.TransactionManager.StartTransaction())
            {

                BlockTable blkTable = _trans.GetObject(acDb.BlockTableId, OpenMode.ForRead) as BlockTable;
                BlockTableRecord blkRecord;

                if (blkTable.Has(_BorderName))
                {
                    ObjectId BlkRecId = blkTable[_BorderName];

                    if (BlkRecId != null)
                    {
                        blkRecord = _trans.GetObject(BlkRecId, OpenMode.ForRead) as BlockTableRecord;

                        ObjectIdCollection blockRefIds = blkRecord.GetBlockReferenceIds(false, false);

                        foreach (ObjectId blockRefId in blockRefIds)
                        {
                            if ((_trans.GetObject(blockRefId, OpenMode.ForRead) as BlockReference).Name == _BorderName && (_trans.GetObject(blockRefId,OpenMode.ForRead) != null))
                            {
                                blkRef = _trans.GetObject(blockRefId, OpenMode.ForRead) as BlockReference;
                            }
                        }
                    }

                }
                _trans.Commit();
            }
            return blkRef; 
        }

(P.S, there might be unnecessary checks/case handling)

 

 

Good news is, the above method does work in some instances.

 

Bad news - in certain drawings, the GetBlockReferenceIds method returns empty. From my understanding, if the block is inserted in the drawing, that collection cannot be empty.

 

Upon further inspection using the MgdDbg tool, the successful drawing happens to have a reference whose Block Table Record match the name I was looking for. However the failed drawing has an different one (even though the Block Table Record I was expecting also existed - but had an empty Reference ID collection). Does that simply mean that the definition for the border I was looking for exist, but was not used? And perhaps whoever made that drawing used a different border whose block definition included the name I wasn't expecting?

 

I am terribly sorry for the confusion, I am fundamentally misunderstanding the way blocks are handled.

If the above code should work for ideal cases, is there a smarter way to find the reference I need without addressing it by name? I could say that the block in question does have distinct attributes I am looking for...

 

 

Thanks for the clarification!

6 REPLIES 6
Message 2 of 7
ActivistInvestor
in reply to: Anonymous

Your code is passing false for both arguments to GetBlockReferenceIds().

 

The first argument ('directOnly') should be true if you only want direct references to the block whose BlockTableRecord you call the method on. If you pass false for directOnly, you will get not only all references to that block, but also references to any other block whose definition directly or indirectly contains a reference to that block.  So, suppose the definition of a block 'A' contains an insertion of block 'B'.  If you call GetBlockReferenceIds() on the BlockTableRecord for block 'B', and pass false for directOnly, you will get back the ids of all references to both block 'A' and 'B'.

 

However, your problem may very well be with the second argument (forceValidity).  In very early versions of the DWG file, blocks did not store a list of references.  If you open such a DWG file, GetBlockReferenceIds() will not by default, build the list of references when it is called, unless you pass true in the forceValidity argument.

 

Blocks that don't store a list of their references may also be common in .DWG files produced by non-Autodesk products as well.

 

So, assuming you want only direct reference to the block whose BlockTableRecord you're calling the method on, you would use:

 

   

GetBlockReferenceIds(true, true);
Message 3 of 7
Anonymous
in reply to: ActivistInvestor

Appreciated - thanks for clearing those arguments up, I never knew what they were intended for.

 

On the other hand, I have a feeling that's not the problem I am facing. Setting the arguments to false still returns an empty reference list.

I think clarification for the following can go a long way:

 

- Perhaps I am assuming the Block Record I am searching through has a reference in the first place? By opening the drawing, it seems like that is the very case; but maybe given how the users can add in a new block definition (one that is an almost identical to the first) and end up mutating the drawings with obsolete blocks, maybe I should be looking for the second record - avoiding the assumption that the first block I detect that match my criteria has references in the first place?

 

I need to know if I am understanding the terminology and process right - if the above is true, then perhaps it's no longer enough to search the records by my desired block name -- but rather by indirectly checking if the block in question has a reference, and if that reference has the attribute in question.

In the end of the day, I might end up being forced to search through ALL records regardless of name - It sounds like a tedious process for me to believe, as such, any clarifications are extremely valuable here.

 

EDIT: Allow me to add one more concern I have. Using the MgdDbg tool, I looked up an arbitrary BlockTableRecord and it happened to contain attribute definitions - even more specifically, the one I was seeking. My initial assumption was - only Block References will contain the attribute collection. I don't recall being able to retrieve the attributes from a BlockTableRecord object via code. The GetAttributeCollection methods happens to be part of the BlockReference object only. In fact, is this even a normal/standard occurrence in a DWG?

Message 4 of 7
_gile
in reply to: Anonymous

Hi,

 

Perhaps some blocks are dynamic blocs, if so, the code you posted does not search for anonymous block references. Try like this:

 

        public IEnumerable<ObjectId> GetAllBlockReferenceIds(Database db, string blockName)
        {
            using (var tr = db.TransactionManager.StartOpenCloseTransaction())
            {
                var bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
                if (bt.Has(blockName))
                {
                    var btr = (BlockTableRecord)tr.GetObject(bt[blockName], OpenMode.ForRead);
                    foreach (ObjectId brId in btr.GetBlockReferenceIds(true, true))
                    {
                        yield return brId;
                    }
                    if (btr.IsDynamicBlock)
                    {
                        foreach (ObjectId btrId in btr.GetAnonymousBlockIds())
                        {
                            var anonymousBtr = (BlockTableRecord)tr.GetObject(btrId, OpenMode.ForRead);
                            foreach (ObjectId brId in anonymousBtr.GetBlockReferenceIds(true, true))
                            {
                                yield return brId;
                            }
                        }
                    }
                }
            }
        }


Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 5 of 7
ActivistInvestor
in reply to: Anonymous

If using true for both arguments to GetBlockReferenceIds() does not bear fruit, then there are only two other possibilities, the one @_gile mentions, or there are multiple blocks involved and you will need to sort them out using their attributes.

 

Dynamic blocks can be comprised of one or more block definitions. There is the main or 'dynamic' block definition, that the user creates, and there can be any number of additional 'anonymous' block definitions, that have gibberish names generated by AutoCAD. Insertions of dynamic blocks can reference either the main block definition, or one of the anonymous block definitions.

 

You can use the code @_gile posted, although I prefer organizing my code using extension methods, so below is a slightly different approach that takes the form of an extension method for the BlockTableRecord class.

 

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Autodesk.AutoCAD.DatabaseServices
{
   public static class BlockTableRecordExtensions
   {
      public static IEnumerable<ObjectId> GetDynamicBlockReferencIds(
         this BlockTableRecord btr,
         bool directOnly = true)
      {
         if(btr == null)
            throw new ArgumentNullException("btr");
         using(Transaction tr = btr.Database.TransactionManager.StartOpenCloseTransaction())
         {
            var ids = btr.GetBlockReferenceIds(directOnly, true);
            int cnt = ids.Count;
            for(int i = 0; i < cnt; i++)
            {
               yield return ids[i];
            }
            if(btr.IsDynamicBlock)
            {
               BlockTableRecord btr2 = null;
               var blockIds = btr.GetAnonymousBlockIds();
               cnt = blockIds.Count;
               for(int i = 0; i < cnt; i++)
               {
                  btr2 = (BlockTableRecord) tr.GetObject(blockIds[i], OpenMode.ForRead);
                  ids = btr2.GetBlockReferenceIds(directOnly, true);
                  int cnt2 = ids.Count;
                  for(int j = 0; j < cnt2; j++)
                  {
                     yield return ids[j];
                  }
               }
            }
            tr.Commit();
         }
      }
   }
}
Message 6 of 7
Anonymous
in reply to: _gile

MgdDbg confirms it as a dynamic block, so I guess I will be trying that out as soon as possible. Thanks for pointing that out!

Message 7 of 7
Anonymous
in reply to: _gile

I already have the thing working by brutally searching the model space for every attribute reference and if it contains the thing I need. It seems to work.

Here is an attempt with the dynamic block consideration as you guys suggested - I decided to condense it into 1 function to test it for my specific scenario:

 

        private BlockReference GetDynamicBlockReference(string _BorderName)
        {
            BlockReference borderRef = null;
            BlockTableRecord blkRec = null;

            using (Transaction _trans = acDb.TransactionManager.StartTransaction())
            {
                BlockTable blkTable = _trans.GetObject(acDb.BlockTableId, OpenMode.ForRead) as BlockTable;
                if(blkTable.Has(_BorderName))
                {
                    blkRec = _trans.GetObject(blkTable[_BorderName],OpenMode.ForRead) as BlockTableRecord;
                    
                    // Iterate and check if one of the references resembles the border
                    // if not, check if Dynamic.
                SEARCH_NAME:

                    foreach(ObjectId refID in blkRec.GetBlockReferenceIds(true,true))
                    {
                        if ((_trans.GetObject(refID, OpenMode.ForRead) as BlockReference).Name == _BorderName)
                        {
                            borderRef = _trans.GetObject(refID, OpenMode.ForRead) as BlockReference;
                            break;
                        }

                    }

                    // Occurs when the standard block references are
                    // empty - or don't contain the desired border.
                    if (blkRec.IsDynamicBlock)
                    {
                        foreach (ObjectId refID in blkRec.GetAnonymousBlockIds())
                        {
                            blkRec = _trans.GetObject(refID, OpenMode.ForRead) as BlockTableRecord;
                            goto SEARCH_NAME;
                        }

                    }

                }

                _trans.Commit();
            }

            return borderRef;
        }

Please excuse the goto, it was simply cutting corners - especially since the specific drawing returned one ID in the first place.

 

 

Results: Debugging shows that it acquired the correct border reference, the one that contains the correct attribute and the one in use in the drawing. However, because of the random looking name it had, it failed my name check. At this point, it seems like I am searching the attributes either ways - is that correct? Nonetheless, this was very helpful to understand.

 

Let me know if I am missing anything and as to whether I can still salvage this.

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

Post to forums  

AutoCAD Inside the Factory


Autodesk Design & Make Report