Problems when making blocks anonymous

Problems when making blocks anonymous

max.senft
Enthusiast Enthusiast
1,984 Views
9 Replies
Message 1 of 10

Problems when making blocks anonymous

max.senft
Enthusiast
Enthusiast

Hi everybody,

 

I've got a new delicate problem to solve and I currently don't know what this might tell me or how to solve it...

 

At first. What I've got: It's a function that copies a BlockTableRecord to which a BlockReference is pointing to, makes it anonymous and changes the BlockReference so that it points to the new anonymous copy. Here's the code:

 

var doc = Application.DocumentManager.MdiActiveDocument;
var db = doc.Database;
var pOrigBR = tr.GetObject(origId, OpenMode.ForRead) as BlockReference;
var origBTR = (BlockTableRecord)tr.GetObject(pOrigBR.BlockTableRecord, OpenMode.ForRead);
if ((origBTR.GetBlockReferenceIds(true, false).Count > 1) ||
    (origBTR.IsAnonymous == false))
{
    // Clone Block
    using (var cloneDb = db.Wblock(origBTR.ObjectId))
    {
        #region Check for Dynamic Properties
        // Search and delete "ACAD_ENHANCEDBLOCK" in the Extension Dictionary
        using (var cloneDbTr = cloneDb.TransactionManager.StartTransaction())
        {
            var cloneModelSpaceBTR = (BlockTableRecord)cloneDbTr.GetObject(SymbolUtilityServices.GetBlockModelSpaceId(cloneDb), OpenMode.ForRead);
            if (cloneModelSpaceBTR.ExtensionDictionary.IsNull == false)
            {
                var cloneExtDict = (DBDictionary)cloneDbTr.GetObject(cloneModelSpaceBTR.ExtensionDictionary, OpenMode.ForWrite);
                if (cloneExtDict.Contains("ACAD_ENHANCEDBLOCK"))
                {
                    cloneExtDict.Remove("ACAD_ENHANCEDBLOCK");
                }
            }

            cloneDbTr.Commit();
        }
        #endregion

        string newName = "*U";
        var newId = db.Insert(newName, cloneDb, true);

        pOrigBR.UpgradeOpen();
        pOrigBR.BlockTableRecord = newId;
        pOrigBR.DowngradeOpen();
    }
}

So far, this piece of code works like a charm. If a block that was created that way, gets copied using the AutoCAD "COPY" command and is processed by that code, the resulting name for the block gets "+U" instead of the usual "*Uxxx" (where xxx is a number).

 

Any further processing of blocks/block references in that state will fail and all blocks will only get the "+U" name.

 

This behaviour can be fixed (does not work all the time) by restarting AutoCAD. But this does not always help. 

 

Does anyone know where the "+U" comes from or what it might tell me? Some file database problems? An internal AutoCAD problem?

 

Would be great if anyone had hints for me! 🙂

 

Best regards

Max

0 Likes
1,985 Views
9 Replies
Replies (9)
Message 2 of 10

ActivistInvestor
Mentor
Mentor

@max.senft wrote:

Hi everybody,

 

I've got a new delicate problem to solve and I currently don't know what this might tell me or how to solve it...

 

At first. What I've got: It's a function that copies a BlockTableRecord to which a BlockReference is pointing to, makes it anonymous and changes the BlockReference so that it points to the new anonymous copy. Here's the code:

 

 

Best regards

Max


You might get more help if instead of excerpts from your code, you posted a more-complete, reproducible case (code and data/DWG).

 

 

0 Likes
Message 3 of 10

max.senft
Enthusiast
Enthusiast

Hi,

 

the code is the function (without the function head) that does the explained work. There's a selection before that function and nothing afterwards.

 

I hoped that the "+U" name that is shown by AutoCAD could tell me something but try search for "AutoCAD block +U" in Google or here in the forum. If you find an appropriate result you are my hero! 🙂

 

Regarding the reproducible case: The problem does not occur all the time. We could not find a connection between AutoCAD instance / PC / drawing / amount of work done in an AutoCAD session, yet.

 

This "+U" block name evidence is just the first thing that was always the same...

 

Regards

Max

0 Likes
Message 4 of 10

ActivistInvestor
Mentor
Mentor

What I would suggest is to clone the insertion of the block into another new Database, and then if the block is dynamic, call ConvertToStaticBlock(), and then rename the block definition and then clone the result back into the source Database.

 


@max.senft wrote:

Hi,

 

the code is the function (without the function head) that does the explained work. There's a selection before that function and nothing afterwards.

 

I hoped that the "+U" name that is shown by AutoCAD could tell me something but try search for "AutoCAD block +U" in Google or here in the forum. If you find an appropriate result you are my hero! 🙂

 

Regarding the reproducible case: The problem does not occur all the time. We could not find a connection between AutoCAD instance / PC / drawing / amount of work done in an AutoCAD session, yet.

 

This "+U" block name evidence is just the first thing that was always the same...

 

Regards

Max




 

 

 

 

0 Likes
Message 5 of 10

max.senft
Enthusiast
Enthusiast

Hi,

 

just to recap, and I get what your idea is:

 

  1. Wblock the original BlockTableRecord into a new database - like I already do.
  2. Insert the BlockTableRecord into a second new database.
  3. Call ConvertToStaticBlock() if BTR is dynamic.
  4. Rename the BTR to "*U".
  5. Insert the BTR from step 4 into the original database.

Right?

 

Regards

Max

0 Likes
Message 6 of 10

ActivistInvestor
Mentor
Mentor

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:

 

  1. Wblock the original BlockTableRecord into a new database - like I already do.
  2. Insert the BlockTableRecord into a second new database.
  3. Call ConvertToStaticBlock() if BTR is dynamic.
  4. Rename the BTR to "*U".
  5. Insert the BTR from step 4 into the original database.

Right?

 

Regards

Max


 

0 Likes
Message 7 of 10

max.senft
Enthusiast
Enthusiast

Hi,

 

well, the Wblock is just a convenience function of WblockClone or WblockCloneObjects. I cannot see any difference in your code snippet regarding the outcome, but the final WblockCloneObjects back into the original database instead of an insert.

 

I also used this as reference: http://help.autodesk.com/view/OARX/2018/ENU/?guid=GUID-EF25763C-8E3B-41AB-8D47-5F280BBD77DD

 

So, at the moment I don't see that AutoCAD processes an Insert(...) call differently to your WblockCloneObjects(...) call on the lower level.

 

But maybe WblockCloneObjects does less "unknown" stuff and might be the better choice though. I guess I will give it a try anyway...

 

Max.

0 Likes
Message 8 of 10

ActivistInvestor
Mentor
Mentor

Insert() doesn't follow hard pointer references, while WblockCloneObjects() does, and anything attached to the BlockTableRecord (such as objects in an extension dictionary, or anything they reference with a hard pointer reference) may not be cloned back to the new copy of the BlockTableRecord, and/or may not have ObjectIds translated correctly.

 

So, there is a big difference between cloning the contents of a block into the model space of another Database and then using Insert(), and using WblockCloneObjects() in the way I showed.

 


@max.senft wrote:

Hi,

 

well, the Wblock is just a convenience function of WblockClone or WblockCloneObjects. I cannot see any difference in your code snippet regarding the outcome, but the final WblockCloneObjects back into the original database instead of an insert.

 

I also used this as reference: http://help.autodesk.com/view/OARX/2018/ENU/?guid=GUID-EF25763C-8E3B-41AB-8D47-5F280BBD77DD

 

So, at the moment I don't see that AutoCAD processes an Insert(...) call differently to your WblockCloneObjects(...) call on the lower level.

 

But maybe WblockCloneObjects does less "unknown" stuff and might be the better choice though. I guess I will give it a try anyway...

 

Max.


 

0 Likes
Message 9 of 10

max.senft
Enthusiast
Enthusiast

Hi,

 

I finally tried your code but ran into a new problem. It seems like the draw order of the block is not correctly retained:

 

Left: Correct block; Right: Incorrect blockLeft: Correct block; Right: Incorrect block

The left block is the original one, the right one is the cloned block. Both contain the same entities, but it seems that the cloned "ACAD_SORTENTS" extension dictionary does not contain any entity references anymore:

 

Broken SORTENTSBroken SORTENTSWorking SORTENTSWorking SORTENTS

Entities in original blockEntities in original block

How could that be fixed? Manually copy the ACAD_SORTENTS dictionary entries?

 

Regards

Max

 

0 Likes
Message 10 of 10

ActivistInvestor
Mentor
Mentor

@max.senft wrote:

Hi,

 

I finally tried your code but ran into a new problem. It seems like the draw order of the block is not correctly retained:

 

 

The left block is the original one, the right one is the cloned block. Both contain the same entities, but it seems that the cloned "ACAD_SORTENTS" extension dictionary does not contain any entity references anymore:

 

 

 

How could that be fixed? Manually copy the ACAD_SORTENTS dictionary entries?

 

Regards

Max

 


 

I don't recall ever having used that method in a scenario where draw order was being used, and so I never even realized it wasn't cloning the draworder table.

 

So, I guess the answer is that it must be done manually.

 

See the revised version below, which should do it.

 

 

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.Replace, 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);
            IdMapping map2 = new IdMapping();
            dbTemp.WblockCloneObjects(ids, db.BlockTableId, map2, DuplicateRecordCloning.Replace, false);
            if(!(map2.Contains(cloneId) && map2[cloneId].IsCloned))
               throw new InvalidOperationException($"Failed to clone BlockTableRecord {btr.Name}.");
            CloneDrawOrder(btr, map2[cloneId].Value, map, map2);
            return map2[cloneId].Value;
         }
      }


      static void CloneDrawOrder(BlockTableRecord src, ObjectId destBtrId, IdMapping map1, IdMapping map2)
      {
         using(var tr = new OpenCloseTransaction())
         {
            BlockTableRecord dest = (BlockTableRecord) tr.GetObject(destBtrId, OpenMode.ForRead);
            DrawOrderTable dotsrc=(DrawOrderTable) tr.GetObject(src.DrawOrderTableId, OpenMode.ForRead);
            var ids = dotSrc.GetFullDrawOrder(0);
            if(ids.Count > 0)
            {
               ObjectIdCollection newIds = new ObjectIdCollection();
               DrawOrderTable dotDest = (DrawOrderTable) tr.GetObject(dest.DrawOrderTableId, OpenMode.ForWrite);
               foreach(ObjectId id in ids)
               {
                  newIds.Add(map2[map1[id].Value].Value);
               }
               dotDest.SetRelativeDrawOrder(newIds);
            }
            tr.Commit();
         }

      }
   }
}
0 Likes