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

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

5 REPLIES 5
SOLVED
Reply
Message 1 of 6
bsaidiDG51
643 Views, 5 Replies

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

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();
     }
 }

 

5 REPLIES 5
Message 2 of 6
_gile
in reply to: bsaidiDG51

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

Message 3 of 6
bsaidiDG51
in reply to: _gile

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);

 

Message 4 of 6
_gile
in reply to: bsaidiDG51

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

Message 5 of 6
CLL_PBE
in reply to: _gile

Hi Gile

Can this be used on multiple files?
Message 6 of 6
_gile
in reply to: CLL_PBE


@CLL_PBE wrote:
Hi Gile

Can this be used on multiple files?

Yes, for sure.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

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