How to ATTSYNC block references after a change in the block definition programmatically in C#

bsaidiDG51
Enthusiast
Enthusiast

How to ATTSYNC block references after a change in the block definition programmatically in C#

bsaidiDG51
Enthusiast
Enthusiast

ed.Regen() in the code below only regenerates the physical/visual aspect of the block references, but it doesn't synchronize with the attributes in the new block definition. So is there an equivalent of the command ATTSYNC in C#? Thank you.

 

 

 

 [CommandMethod("ReplaceBlock")]
 public void UpdateBlock()
 {
     Document doc = Application.DocumentManager.MdiActiveDocument;
     Database db = doc.Database;
     Editor ed = doc.Editor;
     

     var blkFile = "C:\\8x11Materiel.dwg";
     string blkName = System.IO.Path.GetFileNameWithoutExtension(blkFile);

     // read the block file into a database
     using (var sourceDb = new Database(false, true))
     {
         sourceDb.ReadDwgFile(blkFile, FileOpenMode.OpenForReadAndAllShare, false, null);
         db.Insert(blkName, sourceDb, false);
     }

     RemoveAttributeFromBlock(db, blkName);

     ed.Regen();
 }

 public static void RemoveAttributeFromBlock(Database db, string blockName)
 {
     using (Transaction tr = db.TransactionManager.StartTransaction())
     {
         // Get the block definition
         var blockTable = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
         if (!blockTable.Has(blockName))
             return;
         var blockDefinition = (BlockTableRecord)tr.GetObject(blockTable[blockName], OpenMode.ForWrite);

         // Iterate through the attribute definitions and remove them
         foreach (ObjectId objectId in blockDefinition)
         {
             DBObject obj = tr.GetObject(objectId, OpenMode.ForWrite);
             if (obj is AttributeDefinition attributeDefinition)
             {
                 attributeDefinition.Erase();
             }
         }

         // Commit the transaction
         tr.Commit();
     }
 }

 

0 Likes
Reply
Accepted solutions (1)
644 Views
5 Replies
Replies (5)

_gile
Mentor
Mentor
Accepted solution

Hi,

 

Here's a simple method which erases the existing AttributeReferences from the inserted BlockReferences and adds them new ones from the BlockTableRecord AttributeDefinitions.

 

The SynchronizeAttributes() method is defined as an extension method for the BlockTableRecord type so that it can be called as an instance method of this type. These methods have to be called in the scope of a standard Transaction (not an OpenCloseTransaction). 
They are extracted from Gile.AutoCAD.Extension library.

 

Example (assuming btr is a BlocTableRecord instance) : btr.SynchronizeAttributes()

 

public static class Extension
{
    /// <summary>
    /// Synchronizes the attributes of all block references.
    /// </summary>
    /// <param name="target">Instance which the method applies.</param>
    /// <exception cref="System.ArgumentNullException">Thrown if <paramref name ="target"/> is null.</exception>
    public static void SynchronizeAttributes(this BlockTableRecord target)
    {
        if (target == null)
            throw new ArgumentNullException(nameof(target));

        var attDefs = target
            .Cast<ObjectId>()
            .Where(id => id.ObjectClass.Name == "AcDbAttributeDefinition")
            .Select(id => (AttributeDefinition)id.GetObject(OpenMode.ForRead))
            .ToArray();
        foreach (var br in target
                           .GetBlockReferenceIds(true, false)
                           .Cast<ObjectId>()
                           .Select(id => (BlockReference)id.GetObject(OpenMode.ForWrite)))
        {
            br.ResetAttributes(attDefs);
        }
        if (target.IsDynamicBlock)
        {
            target.UpdateAnonymousBlocks();
            foreach (var btr in target
                                .GetAnonymousBlockIds()
                                .Cast<ObjectId>()
                                .Select(id => (BlockTableRecord)id.GetObject(OpenMode.ForWrite)))
            {
                attDefs = target
                          .Cast<ObjectId>()
                          .Where(id => id.ObjectClass.Name == "AcDbAttributeDefinition")
                          .Select(id => (AttributeDefinition)id.GetObject(OpenMode.ForRead))
                          .ToArray();
                foreach (var br in btr
                                   .GetBlockReferenceIds(true, false)
                                   .Cast<ObjectId>()
                                   .Select(id => (BlockReference)id.GetObject(OpenMode.ForWrite)))
                {
                    br.ResetAttributes(attDefs);
                }
            }
        }
    }

    /// <summary>
    /// Resets the attribute references keeping their values.
    /// </summary>
    /// <param name="target">Instance to which the method applies.</param>
    /// <param name="attDefs">Sequence of attribute definitions.</param>
    /// <exception cref="ArgumentNullException">Thrown if <paramref name ="target"/> is null.</exception>
    /// <exception cref="ArgumentNullException">Thrown if <paramref name ="attDefs"/> is null.</exception>
    internal static void ResetAttributes(this BlockReference target, IEnumerable<AttributeDefinition> attDefs)
    {
        if (target == null)
            throw new ArgumentNullException(nameof(target));
        if (attDefs == null)
            throw new ArgumentNullException(nameof(attDefs));

        var attValues = new Dictionary<string, string>();
        foreach (var attRef in target
                               .AttributeCollection
                               .Cast<ObjectId>()
                               .Select(id => (AttributeReference)id.GetObject(OpenMode.ForWrite)))
        {
            attValues.Add(
                attRef.Tag,
                attRef.IsMTextAttribute ? attRef.MTextAttribute.Contents : attRef.TextString);
            attRef.Erase();
        }
        foreach (var attDef in attDefs)
        {
            var attRef = new AttributeReference();
            attRef.SetAttributeFromBlock(attDef, target.BlockTransform);
            if (attDef.Constant)
            {
                attRef.TextString = attDef.IsMTextAttributeDefinition ?
                    attDef.MTextAttributeDefinition.Contents :
                    attDef.TextString;
            }
            else if (attValues.ContainsKey(attDef.Tag))
            {
                attRef.TextString = attValues[attDef.Tag];
            }
            target.AttributeCollection.AppendAttribute(attRef);
            target.Database.TransactionManager.AddNewlyCreatedDBObject(attRef, true);
        }
    }
}

 

 

 

 



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

bsaidiDG51
Enthusiast
Enthusiast

Thank you for your input.

 

First try: When I run the command below (which inserts the new blocks and then it calls your function SynchronizeAttributes()), the block definition becomes the same as the inserted blocks but it doesn't synchronize the attributes of the inserted block references.

 

[CommandMethod("ReplaceBlock")]
public void UpdateBlock()
{
    Document doc = Application.DocumentManager.MdiActiveDocument;
    Database db = doc.Database;
    Editor ed = doc.Editor;


    var blkFile = "C:\\8x11Materiel.dwg";
    string blockName = System.IO.Path.GetFileNameWithoutExtension(blkFile);

    // read the block file into a database
    using (var sourceDb = new Database(false, true))
    {
        sourceDb.ReadDwgFile(blkFile, FileOpenMode.OpenForReadAndAllShare, false, null);
        db.Insert(blockName, sourceDb, false);
    }

    ed.Regen();


    using (Transaction tr = db.TransactionManager.StartTransaction())
    {
        // Get the block definition
        var blockTable = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
        if (!blockTable.Has(blockName))
            return;
        var blockDefinition = (BlockTableRecord)tr.GetObject(blockTable[blockName], OpenMode.ForWrite);

        blockDefinition.SynchronizeAttributes();
    }
}

 

 

 

Second try: When I run the command below (Which removes an attribute called "FEUILLE" from the block definition, then calls your function SynchronizeAttributes(), and then inserts the new blocks), I get this error and AutoCad crashes/exits.

bsaidiD4GJM_0-1698157889002.png

 

[CommandMethod("ReplaceBlock")]
public void UpdateBlock()
{
    Document doc = Application.DocumentManager.MdiActiveDocument;
    Database db = doc.Database;
    Editor ed = doc.Editor;


    var blkFile = "C:\\8x11Materiel.dwg";
    string blockName = System.IO.Path.GetFileNameWithoutExtension(blkFile);

    RemoveAttributeFromBlock(db, blockName);

    // read the block file into a database
    using (var sourceDb = new Database(false, true))
    {
        sourceDb.ReadDwgFile(blkFile, FileOpenMode.OpenForReadAndAllShare, false, null);
        db.Insert(blockName, sourceDb, false);
    }

    //ed.Regen();

}

public static void RemoveAttributeFromBlock(Database db, string blockName)
{
    using (Transaction tr = db.TransactionManager.StartTransaction())
    {
        // Get the block definition
        var blockTable = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
        if (!blockTable.Has(blockName))
            return;
        var blockDefinition = (BlockTableRecord)tr.GetObject(blockTable[blockName], OpenMode.ForWrite);

        // Iterate through the attribute definitions and remove them
        foreach (ObjectId objectId in blockDefinition)
        {
            DBObject obj = tr.GetObject(objectId, OpenMode.ForWrite);
            if (obj is AttributeDefinition attributeDefinition)
            {
                if (attributeDefinition.Tag == "FEUILLE")
                {
                    attributeDefinition.Erase();
                }
            }
        }

        // Commit the transaction
        tr.Commit();

        blockDefinition.SynchronizeAttributes();
    }
}

 

 

I know that code line below would solve my issue, but I prefer to process my drawings without the need of opening them, therefore I prefer using doc.Database. But if it's not possible to use doc.Database for this issue or if it is too complicated, you can just tell me and I'll abandon and go on with the line below:

doc.SendStringToExecute("(command \"ATTSYNC\" \"N\" \"8x11Materiel\")", true, false, false);

 

0 Likes

_gile
Mentor
Mentor

You have to Commit the transaction after having called SynchronizeAttribute.

You should check if the block table does not already contain a bloc named "8x11Materiel" before inserting the Database.

As you simply erase one attribute from the block definition, you could synchronize attributes as in this previous topic.

 

        [CommandMethod("ReplaceBlock")]
        public static void UpdateBlock()
        {
            Document doc = Application.DocumentManager.MdiActiveDocument;
            Database db = doc.Database;
            Editor ed = doc.Editor;


            var blkFile = "C:\\8x11Materiel.dwg";
            string blockName = System.IO.Path.GetFileNameWithoutExtension(blkFile);

            using (var tr = db.TransactionManager.StartTransaction())
            {
                // Check if the block table contains "8x11Materiel"
                var blockTable = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
                ObjectId btrId;
                if (blockTable.Has(blockName))
                {
                    btrId = blockTable[blockName];
                }
                else
                {
                    using (var sourceDb = new Database(false, true))
                    {
                        sourceDb.ReadDwgFile(blkFile, FileOpenMode.OpenForReadAndAllShare, false, null);
                        btrId = db.Insert(blockName, sourceDb, false);
                    }
                }

                // Remove the attribute definition
                var blockDefinition = (BlockTableRecord)tr.GetObject(btrId, OpenMode.ForWrite);
                foreach (ObjectId id in blockDefinition)
                {
                    if (id.ObjectClass.Name == "AcDbAttributeDefinition")
                    {
                        var attributeDefinition = (AttributeDefinition)tr.GetObject(id, OpenMode.ForRead);
                        if (attributeDefinition.Tag == "FEUILLE")
                        {
                            tr.GetObject(id, OpenMode.ForWrite);
                            attributeDefinition.Erase();
                        }
                    }
                }

                // Synchronize attribute of already inserted block references
                // by calling: blockDefinition.SynchronizeAttributes()
                // or simply running the following code
                foreach (ObjectId brId in blockDefinition.GetBlockReferenceIds(true, false))
                {
                    var blockReference = (BlockReference)tr.GetObject(brId, OpenMode.ForWrite);
                    foreach (ObjectId id in blockReference.AttributeCollection)
                    {
                        var attributeReference = (AttributeReference)tr.GetObject(id, OpenMode.ForRead);
                        if (attributeReference.Tag == "FEUILLE")
                        {
                            tr.GetObject(id, OpenMode.ForWrite);
                            attributeReference.Erase();
                        }
                    }
                }

                tr.Commit();
            }
        }

 

 



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

CLL_PBE
Contributor
Contributor
Hi Gile

Can this be used on multiple files?
0 Likes

_gile
Mentor
Mentor

@CLL_PBE wrote:
Hi Gile

Can this be used on multiple files?

Yes, for sure.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub