Understanding how blocks and dynamic blocks work

Understanding how blocks and dynamic blocks work

nshupeFMPE3
Advocate Advocate
2,010 Views
10 Replies
Message 1 of 11

Understanding how blocks and dynamic blocks work

nshupeFMPE3
Advocate
Advocate

I have some code that I have written, which is working, but I feel I'm losing the full understanding when it comes to blocks in AutoCAD and how they work in the .NET API. 

There are block definitions, which reside in the Block Table, and are the blueprints.
There are block references which are "copies" of the definition. I start getting a little confused around BlockReference and BlockTableRecord. Are both BlockDefinitions and BlockReferences different types of BlockTableRecords? I know a Block Reference is the in space representation of a block, and it has a block table record property. Is that property a copy of the Block Definitions Block Table Record?

And how does this work with Dynamic blocks. When I use GetAnonymousBlockIds, am I getting Block References or Block Table Records? 


Here is my code. As I said its working, I just want to make sure my understanding is robust enough to make my code robust for issues I might not have run into yet. 
I'm going through an ObjectIdCollection and changing the layer of entities, and if its a block I am changing the layer for the entities contained inside it. 

static bool CheckEntLayer(Entity ent, Transaction tr, out KeyValuePair<ObjectId, string> val)
{
    val = new KeyValuePair<ObjectId, string>();
    string entLayer = ent.Layer;

    if (HandleBlock(ent, tr)) return false;

    if (!_destinationLayers.Contains(entLayer))
    {
        if (_layerConversions.TryGetValue(entLayer, out string newLayer))
        {
            if (!_idLayerPairs.ContainsKey(ent.ObjectId)) val = new KeyValuePair<ObjectId, string>(ent.ObjectId, newLayer);
        }
        else
        {
            if (ent.EntityColor.IsByLayer && _layerColors.TryGetValue(entLayer, out var layerColor))
            {
                ent.UpgradeOpen();
                ent.Color = layerColor;
            }

            if (!_idLayerPairs.ContainsKey(ent.ObjectId)) val = new KeyValuePair<ObjectId, string>(ent.ObjectId, "0");
        }

        ent.UpgradeOpen();
        ent.Layer = "0";
    }
    else
    {
        if(!_idLayerPairs.ContainsKey(ent.ObjectId)) val = new KeyValuePair<ObjectId, string>(ent.ObjectId, entLayer);
    }

    return true;
}

static bool HandleBlock(Entity ent, Transaction tr)
{
    if (ent.IsDerivedFrom(typeof(BlockReference)))
    {
        Dictionary<ObjectId, string> blockPieces = new Dictionary<ObjectId, string>();
        
        BlockReference block = ent as BlockReference;
        ObjectId btrId = block.IsDynamicBlock ? block.DynamicBlockTableRecord : block.BlockTableRecord;
        BlockTableRecord dynBtr = tr.GetObject(btrId, OpenMode.ForRead, false, true) as BlockTableRecord;

        foreach (ObjectId id in dynBtr)
        {
            id.TryGetRead<Entity>(tr, (idEnt, tran) =>
            {
                if(CheckEntLayer(idEnt, tr, out KeyValuePair<ObjectId, string> piece))
                    blockPieces.Add(piece.Key, piece.Value);
            });
        }

        foreach (ObjectId id in block.AttributeCollection)
        {
            id.TryGetRead<AttributeReference>(tr, att =>
            {
                if (CheckEntLayer(att, tr, out KeyValuePair<ObjectId, string> piece))
                    blockPieces.Add(piece.Key, piece.Value);
                att.RecordGraphicsModified(true);
            });
        }

        ModifyBlockReferences(tr, block, dynBtr);

        _blockIdLayerPairs.Add(ent.ObjectId, blockPieces);
        return true;
    }

    return false;
}

private static void ModifyBlockReferences(Transaction tr, BlockReference block, BlockTableRecord dynBtr)
{
    if (block.IsDynamicBlock)
    {
        var anonymousBlocks = dynBtr.GetAnonymousBlockIds();

        anonymousBlocks.TryForEachWrite<BlockTableRecord>(tr, @Anonymous =>
        {
            foreach (ObjectId id in @Anonymous)
            {
                id.TryGetRead<Entity>(tr, refEnt =>
                {
                    if (CheckEntLayer(refEnt, tr, out KeyValuePair<ObjectId, string> val))
                        _idLayerPairs.Add(val.Key, val.Value);
                    refEnt.RecordGraphicsModified(true);
                });
            }
        });
    }
    else
    {
        dynBtr.GetBlockReferenceIds(true, true)
            .TryForEachWrite<BlockReference>(tr, @Anonymous => { @Anonymous.RecordGraphicsModified(true); });
    }
}



0 Likes
Accepted solutions (1)
2,011 Views
10 Replies
Replies (10)
Message 2 of 11

norman.yuan
Mentor
Mentor
if (ent.IsDerivedFrom(typeof(BlockReference)))

 

this would always is False: Entity IS OT derived from BlockReference. It is the other way around.

 

Norman Yuan

Drive CAD With Code

EESignature

0 Likes
Message 3 of 11

ActivistInvestor
Mentor
Mentor

 

 

ent.IsDerivedFrom(typeof(BlockReference))

 

 

 

 is the same as

 

 

 

ent is BlockReference

 

 

 

The main difference between the two is that the former can be used when the type is not known at compile time, and is stored in a variable.

 

The latter is the preferred way to do it when the type is known at compile time.

 

EDIT:

 

After looking it again, the object assigned to 'ent' could be any type of entity, and it looks like the IsDerivedFrom() in the OP's code is his own extension method, which in that case would be correct, because it isn't comparing typeof(Entity) to BlockReference, it is comparing the result of ent.GetType() to typeof(BlockReference).

 

@nshupeFMPE3 : This isn't a TV game show. Are you offering a prize to whomever can guess what the undisclosed homegrown methods used in your code do exactly?'

 

 

0 Likes
Message 4 of 11

ActivistInvestor
Mentor
Mentor

A brief description of what your code attempts to achieve would be helpful in understanding it because I looked at it and really couldn't make sense of exactly what it is trying to do.

 

The other issue is that you are using a lot of apis and helper functions that are not shown, although it is fairly obvious what some of them do, IsDerivedFrom() misled both myself and @norman.yuan). Please don't post incomplete code,  because it serves no constructive purpose. 

 

Also regarding your analogy, as one who goes back to the pre-cad days of t-squares, straight edges and diazo machines, it is the blueprints that are the copies, and the Vellum / mylar that is the original.

 

BlockTableRecords contain the definition of a block. BlockReferences are replications of a block's definition, at a given location, orientation and scale, which are created using the INSERT command. Blocks are defined using the BLOCK or BEDIT commands.

 

If you modify the contents of a block's definition, all references to it will also be modified.

 

You also mentioned 'BlockDefinition', but I'm not aware of any such API object.

 

0 Likes
Message 5 of 11

nshupeFMPE3
Advocate
Advocate

I apologize for any confusion. I was mainly concerned with understanding how blocks "work" in regards to the .NET API. But I will give an explanation of my code.

the code "starts" here, where I create a dictionary of ObjectId's and strings. 

static void GetLayerPairs(ObjectIdCollection ids)
{
    _idLayerPairs = new Dictionary<ObjectId, string>();
    ids.TryForEachRead<Entity>((ent, tr) =>
    {
        CheckEntLayer(ent, tr, _idLayerPairs);
    });
}

The goal is to prevent layers from a source document from coming into a destination document as I clone objects over to the destination. So I am iterating over each ObjectId in a collection, using a transaction to GetObject() each as an Entity. Then I pass the Entity into CheckEntLayer.

static void CheckEntLayer(Entity ent, Transaction tr, Dictionary<ObjectId, string> dict)
{
    string entLayer = ent.Layer;

    if (HandleBlock(ent, tr)) return;

    if (!_destinationLayers.Contains(entLayer))
    {
        if (_layerConversions.TryGetValue(entLayer, out string newLayer))//preassigned layer conversion
        {
            if (!dict.ContainsKey(ent.ObjectId)) dict.Add(ent.ObjectId, newLayer);
        }
        else
        {
            //no preassigned layer, change color to current layer color before changing to 0 layer
            if (ent.EntityColor.IsByLayer && _layerColors.TryGetValue(entLayer, out var layerColor))
            {
                ent.UpgradeOpen();
                ent.Color = layerColor;
            }

            if (!dict.ContainsKey(ent.ObjectId)) dict.Add(ent.ObjectId, "0");
        }

        ent.UpgradeOpen();
        ent.Layer = "0";
    }
    //else
    //{
    //    //destination has the layer, 
    //    if(!_idLayerPairs.ContainsKey(ent.ObjectId)) val = new KeyValuePair<ObjectId, string>(ent.ObjectId, entLayer);
    //}
}


CheckEntLayer first wants to handle Blocks differently than other layers, as I want to go through each entity within the block to change its containing entities layers. If the Entity is not a block its layer is checked against a couple lists and then the layer it should be on in the destination is stored along with the ObjectId in the dictionary. The layer is changed to the "0" layer since all files have that layer.
If the entity is a block then this code is ran.

static bool HandleBlock(Entity ent, Transaction tr)
{
    if (ent.IsDerivedFrom(typeof(BlockReference)))
    {
        Dictionary<ObjectId, string> blockPieces = new Dictionary<ObjectId, string>();
        
        BlockReference block = ent as BlockReference;
        ObjectId btrId = block.IsDynamicBlock ? block.DynamicBlockTableRecord : block.BlockTableRecord;
        BlockTableRecord dynBtr = tr.GetObject(btrId, OpenMode.ForRead, false, true) as BlockTableRecord;

        foreach (ObjectId id in dynBtr)
        {
            id.TryGetRead<Entity>(tr, (idEnt, tran) => CheckEntLayer(idEnt, tr, blockPieces));
        }

        foreach (ObjectId id in block.AttributeCollection)
        {
            id.TryGetRead<AttributeReference>(tr, att =>
            {
                CheckEntLayer(att, tr, blockPieces);
                att.RecordGraphicsModified(true);
            });
        }

        ModifyBlockReferences(tr, block, dynBtr);

        _blockIdLayerPairs.Add(ent.ObjectId, blockPieces);
        return true;
    }

    return false;
}


First the RxClass of the entity is checked to verify it is a BlockReference at runtime. If so, the BlockTableRecord ObjectId is retrieved depending on if the block is a dynamic block or not. Then each entity of the BlockTableRecord is sent to CheckEntLayer. This would recursively drill down the blocks if there were nested blocks. Then the same is done for the blocks Attributes. And then the block is sent to ModifyBlockReferences

private static void ModifyBlockReferences(Transaction tr, BlockReference block, BlockTableRecord dynBtr)
{
    if (block.IsDynamicBlock)
    {
        var anonymousBlocks = dynBtr.GetAnonymousBlockIds();

        anonymousBlocks.TryForEachWrite<BlockTableRecord>(tr, @Anonymous =>
        {
            foreach (ObjectId id in @Anonymous)
            {
                id.TryGetRead<Entity>(tr, refEnt =>
                {
                    CheckEntLayer(refEnt, tr, _idLayerPairs);
                    refEnt.RecordGraphicsModified(true);
                });
            }
        });
    }
    else
    {
        dynBtr.GetBlockReferenceIds(true, true)
            .TryForEachWrite<BlockReference>(tr, @Anonymous => { @Anonymous.RecordGraphicsModified(true); });
    }
}

This will find the anonymous or other BlockReferences of the block, and edit their BlockTableRecords in the same way using CheckEntLayer.

I start getting confused at GetAnonymousBlockIds(). This returns BlockTableRecord ObjectId's, but I would almost expect it to retrieve the ObjectId's for the BlockReference's that were made from the block definition. 

Is the way it works is when a block is inserted that is from a dynamic block definition, an anonymous BlockTableRecord is created, which then a BlockReference is made from?

0 Likes
Message 6 of 11

ActivistInvestor
Mentor
Mentor

A dynamic block can generate any number of anonymous blocks to represent insertions with varying graphics that cannot be represented by a single block definition. So, to represent the variations an anonymous block is defined, and inserted to represent the dynamic block.

 

GetAnonymousBlockIds() returns the ObjectIds of those anonymous blocks (BlockTableRecords), not references to them (BlockReferences). However, you can take a look at this post which shows code that will get all references to a dynamic block, including all references to all of the anonymous blocks generated for the dynamic block.

0 Likes
Message 7 of 11

nshupeFMPE3
Advocate
Advocate

I read the code you linked, and I think it makes sense to me. 

There are BlockTableRecords, which are the definitions for blocks (the Vellum as I understand from above ^).
If the BlockTableRecord is dynamic, then it can create another BlockTableRecord which has the required graphics.
And then BlockReferences are just in space (model or paper) references to these BlockTableRecords. 

This does create a follow up question. I see how in the code that was linked, one could go from a BlockTableRecord, to an anonymous BlockTableRecord, and then to the references to that anonymous BTR. But if a BlockReference was selected (lets say from model space), it would have IsDynamic equal to true. If you use the BlockTableRecord property of the BlockReference, will you get the anonymous BTR, or the Dynamic "original" one? And is there a way to choose which one you want? 
Is that what this line is doing? 

ObjectId btrId = block.IsDynamicBlock ? block.DynamicBlockTableRecord : block.BlockTableRecord;

 The dynamic BTR is the "original", and the BlockTableRecord is the anonymous version the "original" created to represent the required graphics?

Sorry for all the questions and the deep dive. I want to be able to understand this stuff so I can debug my code properly, but I also find it kind of interesting. Thanks for your help and patience. 

0 Likes
Message 8 of 11

_gile
Consultant
Consultant

Hi,

In addition to the @ActivistInvestor's and @norman.yuan's replies, you should use an inspection tool such as MgbDbg or Inspector to help you understanding the relationship between AutoCAD objects.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

0 Likes
Message 9 of 11

nshupeFMPE3
Advocate
Advocate
Thank you @_gile, I installed MgbDbg and it is quiet the useful tool I can see
0 Likes
Message 10 of 11

ActivistInvestor
Mentor
Mentor
Accepted solution

@nshupeFMPE3 wrote:

I read the code you linked, and I think it makes sense to me. 

There are BlockTableRecords, which are the definitions for blocks (the Vellum as I understand from above ^).
If the BlockTableRecord is dynamic, then it can create another BlockTableRecord which has the required graphics.
And then BlockReferences are just in space (model or paper) references to these BlockTableRecords. 

This does create a follow up question. I see how in the code that was linked, one could go from a BlockTableRecord, to an anonymous BlockTableRecord, and then to the references to that anonymous BTR. But if a BlockReference was selected (lets say from model space), it would have IsDynamic equal to true. If you use the BlockTableRecord property of the BlockReference, will you get the anonymous BTR, or the Dynamic "original" one? And is there a way to choose which one you want? 
Is that what this line is doing? 

 

 

 

ObjectId btrId = block.IsDynamicBlock ? block.DynamicBlockTableRecord : block.BlockTableRecord;

 

 

 


@nshupeFMPE3 wrote:

I read the code you linked, and I think it makes sense to me. 

There are BlockTableRecords, which are the definitions for blocks (the Vellum as I understand from above ^).
If the BlockTableRecord is dynamic, then it can create another BlockTableRecord which has the required graphics.
And then BlockReferences are just in space (model or paper) references to these BlockTableRecords. 

This does create a follow up question. I see how in the code that was linked, one could go from a BlockTableRecord, to an anonymous BlockTableRecord, and then to the references to that anonymous BTR. But if a BlockReference was selected (lets say from model space), it would have IsDynamic equal to true. If you use the BlockTableRecord property of the BlockReference, will you get the anonymous BTR, or the Dynamic "original" one? And is there a way to choose which one you want? 
Is that what this line is doing? 

 

ObjectId btrId = block.IsDynamicBlock ? block.DynamicBlockTableRecord : block.BlockTableRecord;

 

 The dynamic BTR is the "original", and the BlockTableRecord is the anonymous version the "original" created to represent the required graphics?

Sorry for all the questions and the deep dive. I want to be able to understand this stuff so I can debug my code properly, but I also find it kind of interesting. Thanks for your help and patience. 



Correct.  The dynamic block definition is the 'original' (that was created and named by the user), and the anonymous version(s) are generated as needed to accommodate variations that require a different representation.  However, the code you show above isn't necessary. 

 

Typically, applications deal with the dynamic block definition, not the anonymous representations. So, you can just use DynamicBlockTableRecord unconditionally, because if the block reference is not a dynamic block, the DynamicBlockTableRecord property returns the same value returned by the BlockTableRecord property. Hence, the test of IsDynamicBlock is not needed.

0 Likes
Message 11 of 11

ActivistInvestor
Mentor
Mentor

@shockleyrabeeca is an AI-assisted phishing attack. Do not click the link in the post. 

0 Likes