Regarding step 1, that's not what you're doing. You're wblock'ing a block's entities into the model space of another new database, which is not the same as cloning the BlockTableRecord itself into the new database's block table.
The code below will copy an existing block and give it a new name. The block can be a dynamic block or a static block. It might also work with anonymous blocks, but I've never needed it to do that and haven't tested it with them.
using System;
using System.Diagnostics;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
namespace CopyBlockExample
{
public static class CopyBlockExampleCommands
{
/// <summary>
/// Creates a copy of an existing block and gives the copy the specified name.
/// </summary>
[CommandMethod("BCOPY")]
public static void CopyBlockCommand()
{
Document doc = Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
Database db = doc.Database;
PromptEntityOptions peo = new PromptEntityOptions("\nSelect block reference: ");
peo.SetRejectMessage("\nRequires a block reference,");
peo.AddAllowedClass(typeof(BlockReference), false);
var per = ed.GetEntity(peo);
if(per.Status != PromptStatus.OK)
return;
PromptStringOptions pso = new PromptStringOptions("\nNew block name: ");
pso.AllowSpaces = true;
PromptResult pr = ed.GetString(pso);
if(pr.Status != PromptStatus.OK || string.IsNullOrWhiteSpace(pr.StringResult))
return;
try
{
SymbolUtilityServices.ValidateSymbolName(pr.StringResult, false);
}
catch(System.Exception)
{
ed.WriteMessage($"\nInvalid block name: {pr.StringResult}");
return;
}
try
{
string newName = pr.StringResult;
using(Transaction tr = doc.TransactionManager.StartTransaction())
{
var bt = (BlockTable) tr.GetObject(db.BlockTableId, OpenMode.ForRead);
if(bt.Has(newName))
{
ed.WriteMessage("\nA block with the specified name already exists.");
tr.Commit();
return;
}
var blockref = (BlockReference) tr.GetObject(per.ObjectId, OpenMode.ForRead);
ObjectId id = blockref.DynamicBlockTableRecord;
BlockTableRecord btr = (BlockTableRecord) tr.GetObject(id, OpenMode.ForRead);
string name = btr.Name;
ObjectId cloneId = btr.Copy(newName);
btr = (BlockTableRecord) tr.GetObject(cloneId, OpenMode.ForRead);
ed.WriteMessage("\nBlock [{0}] copied to [{1}].", name, btr.Name);
tr.Commit();
}
}
catch(System.Exception ex)
{
ed.WriteMessage("\nOperation failed: {0}", ex.Message);
}
}
}
}
namespace Autodesk.AutoCAD.DatabaseServices
{
public static class BlockTableRecordExtensions
{
/// <summary>
/// Creates a new copy of an existing BlockTableRecord having
/// the specified name.
/// </summary>
/// <remarks>This method creates a copy of a BlockTableRecord
/// by cloning it to a tempoarary database, and then cloning
/// the clone back into the original database after assigning
/// the specified new Name.
///
/// Note: When this method is called, the BlockTable of the
/// database containing the BlockTableRecord the method is
/// invoked on must NOT be open.
/// </remarks>
/// <param name="btr">The BlockTableRecord to be copied.</param>
/// <param name="newName">The name which the new copy of the
/// BlockTableRecord will have. A block with the given name
/// must not already exist. If this argument contains the
/// format placeholder string "{0}", it will be replaced with
/// the name of the existing block that is being cloned.</param>
/// <param name="OnCloned">A delegate accepting a BlockTableRecord
/// as its only argument. If provided and not null, the delegate is
/// called and passed the clone of the source BlockTableRecord that
/// is currently open for write. The delegate can pre-process the
/// clone before it is cloned back into the original database. At
/// the point when this delegate is called, the clone has already
/// been assigned its new name. The BlockTableRecord passed into
/// this method resides in a temporary, in-memory database that is
/// discarded when the operation has completed.</param>
/// <returns>The ObjectId of the new copy of the BlockTableRecord</returns>
public static ObjectId Copy(this BlockTableRecord btr, string newName, Action<BlockTableRecord> OnCloned = null)
{
Assert.IsNotNull(btr, nameof(btr));
if(string.IsNullOrEmpty(newName))
throw new ArgumentException(nameof(newName));
ErrorStatus.NoDatabase.Check(btr.Database != null);
if(btr.IsAnonymous || btr.IsFromExternalReference || btr.IsLayout || btr.IsDependent)
throw new ArgumentException("Invalid block");
if(newName.Equals(btr.Name, StringComparison.CurrentCultureIgnoreCase))
throw new ArgumentException("new and existing names are the same");
Database db = btr.Database;
string name = btr.Name;
if(newName.Contains("{0}"))
{
if(newName.Trim().Equals("{0}"))
throw new ArgumentException($"Invalid block name: {newName}");
newName = string.Format(newName, name);
}
SymbolUtilityServices.ValidateSymbolName(newName, false);
using(var trans = new OpenCloseTransaction())
{
var bt = (BlockTable) trans.GetObject(db.BlockTableId, OpenMode.ForRead);
if(bt.Has(newName))
throw new ArgumentException($"A block with the name \"{newName}\" already exists");
trans.Commit();
}
using(Database dbTemp = new Database(true, true))
{
ObjectIdCollection ids = new ObjectIdCollection();
ids.Add(btr.ObjectId);
var map = new IdMapping();
db.WblockCloneObjects(ids, dbTemp.BlockTableId, map, DuplicateRecordCloning.Ignore, false);
ObjectId cloneId = map[btr.ObjectId].Value;
using(var trans = new OpenCloseTransaction())
{
var clone = (BlockTableRecord) trans.GetObject(cloneId, OpenMode.ForWrite);
clone.Name = newName;
if(OnCloned != null)
{
var workingDB = HostApplicationServices.WorkingDatabase;
HostApplicationServices.WorkingDatabase = dbTemp;
try
{
OnCloned(clone);
}
finally
{
HostApplicationServices.WorkingDatabase = workingDB;
}
}
trans.Commit();
}
ids.Clear();
ids.Add(cloneId);
map = new IdMapping();
dbTemp.WblockCloneObjects(ids, db.BlockTableId, map, DuplicateRecordCloning.Ignore, false);
if(!(map.Contains(cloneId) && map[cloneId].IsCloned))
throw new InvalidOperationException($"Failed to clone BlockTableRecord {btr.Name}.");
return map[cloneId].Value;
}
}
}
}
namespace Autodesk.AutoCAD.Runtime
{
using AcRx = Autodesk.AutoCAD.Runtime;
public static class RuntimeExtensions
{
public static void Check(this ErrorStatus es, bool condition, string msg = null)
{
if(!condition)
{
if(msg == null)
throw new AcRx.Exception(es);
else
throw new AcRx.Exception(es, msg);
}
}
}
}
namespace System.Diagnostics
{
public static class Assert
{
public static void IsNotNull<T>(T obj, string name = null) where T : class
{
if(obj == null)
throw new ArgumentNullException(name ?? "unspecified");
}
}
}
@max.senft wrote:
Hi,
just to recap, and I get what your idea is:
- Wblock the original BlockTableRecord into a new database - like I already do.
- Insert the BlockTableRecord into a second new database.
- Call ConvertToStaticBlock() if BTR is dynamic.
- Rename the BTR to "*U".
- Insert the BTR from step 4 into the original database.
Right?
Regards
Max