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();
}
}
Solved! Go to Solution.
Solved by _gile. Go to 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);
}
}
}
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.
[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);
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();
}
}
Can't find what you're looking for? Ask the community or share your knowledge.