Redefine block self reference error

Redefine block self reference error

nate.diven
Explorer Explorer
737 Views
2 Replies
Message 1 of 3

Redefine block self reference error

nate.diven
Explorer
Explorer

I am trying to create a command named "SYNCBLOCKS" that allows the user to select a block reference and then choose drawings they want to redefine the referenced block in. 

 

I tried to use the method shown in this blog post: https://adndevblog.typepad.com/autocad/2012/05/redefining-a-block.html

 

My Testing Setup: I have a drawing that I open in AutoCAD with a block named "VSE-LAYERS" and a reference to that block in model space. In AutoCAD I run the custom command "SYNCBLOCKS" and select the block reference, and then in the file prompt I select a different file that also has a block named "VSE-LAYERS".

 

Desired Result: The block in the selected file to match the block in the original file.

 

Actual Result: AutoCAD throws a self reference error

Command: SYNCBLOCKS
Syncing blockSelect block to sync:
Selected "VSE-LAYERS"Loading AEC Base...
Loading AEC Base Extended...
Loading AEC Core...
Loading AEC Project Base...
Loading AEC Architectural Base...
Loading AEC Schedule...
Block VSE-LAYERS references itself
Error syncing block in "C:\Users\nate.diven\Desktop\VZW NODE 03.dwg": Autodesk.AutoCAD.Runtime.Exception: eSelfReference
   at Autodesk.AutoCAD.DatabaseServices.Database.Insert(String blockName, Database dataBase, Boolean preserveSourceDatabase)
   at VectorCadCommands.OtherCommands.SyncBlocks() in C:\Users\nate.diven\Documents\GitHub\VectorCadCommands\VectorCadCommands\OtherCommands.cs:line 619

 

So far this is what I have:

 

[CommandMethod("SyncBlocks")]
public void SyncBlocks()
{
    var doc = Application.DocumentManager.MdiActiveDocument;
    var db = doc.Database;
    var ed = doc.Editor;
    ed.WriteMessage("\nSyncing block");

    ObjectId pickedId = ObjectId.Null;

    PromptEntityOptions opt = new PromptEntityOptions("Select block to sync");
    opt.AllowNone = false;

    // prompt user to select block reference
    PromptEntityResult res = ed.GetEntity(opt);
    if (res.Status != PromptStatus.OK)
    {
        ed.WriteMessage("\ninvalid selection");
    }
    pickedId = res.ObjectId;

    string blockName = ""; // will store selected block name
    // string filePath = db.Filename; // this was used for another method I was trying

    using (var tx = db.TransactionManager.StartTransaction())
    {
        BlockReference br = tx.GetObject(pickedId, OpenMode.ForRead) as BlockReference;
        if (br == null)
        {
            ed.WriteMessage("\ninvalid selection");
            return;
        }
        blockName = br.Name;
        tx.Dispose();
    }

    ed.WriteMessage($"\nSelected \"{blockName}\"");

    OpenFileDialog ofd = new OpenFileDialog("Select DWGs to sync", null, "dwg", "DWG Files (*.dwg)|*.dwg", OpenFileDialog.OpenFileDialogFlags.AllowMultiple);

    // prompt user to select drawing files
    var dr = ofd.ShowDialog();
    if (dr != System.Windows.Forms.DialogResult.OK)
    {
        ed.WriteMessage("\nNo files selected");
        return;
    }

    var dm = Application.DocumentManager;

    // loop through each selected file
    foreach (var file in ofd.GetFilenames())
    {
        try
        {
            using(var tempDb = new Database(false, true))
            {
                // this is the database the block needs to be updated in
                tempDb.ReadDwgFile(file, FileOpenMode.OpenForReadAndWriteNoShare, true, null); 

                using (var tx = tempDb.TransactionManager.StartTransaction())
                {
                    var bt = tx.GetObject(tempDb.BlockTableId, OpenMode.ForRead, false, true);
                    var btrId = tempDb.Insert(blockName, db, true);

                    if (btrId != ObjectId.Null)
                    {
                        var btr = tx.GetObject(btrId, OpenMode.ForRead) as BlockTableRecord;
                        var brefIds = btr.GetBlockReferenceIds(false, true);

                        foreach (ObjectId id in brefIds)
                        {
                            var bref = tx.GetObject(id, OpenMode.ForWrite) as BlockReference;
                            bref.RecordGraphicsModified(true);
                        }
                    }

                    tx.Commit();
                }

                tempDb.SaveAs(file, DwgVersion.AC1027);
            }
        }
        catch (System.Exception ex)
        {
            dm.MdiActiveDocument.Editor.WriteMessage($"\nError syncing block in \"{file}\": {ex}");
        }
    }
}

 

 

 

 

0 Likes
Accepted solutions (1)
738 Views
2 Replies
Replies (2)
Message 2 of 3

norman.yuan
Mentor
Mentor
Accepted solution

Well, what your code does is to insert current drawing's database as a block definition, with a given block name, into other drawing files, which you open each of them as  side database. However, the current drawing IS NOT A BLOCK DRAWING FILE: it contains a block definition with the same name. That is, in the other database (the side-opened database), you want to insert the current database with block name ("VSE-LAYERS") while the current database itself has a block definition called "VSE-LAYERS". Thus the self-reference.

 

What you need to do is WblockCloneObjects() the block definition from the current database to the side database. Also, since the side database is not opened in AutoCAD editor, there is no need to find all block references of that updated block definition and call RecordGraphcsModified() at all.

 

 

Norman Yuan

Drive CAD With Code

EESignature

Message 3 of 3

nate.diven
Explorer
Explorer

Thanks for your reply! Now I understand why insert wasn't working. I modified it to use WBlockCloneObjects instead and got it working.

Here's the code if anyone is interested:

 

[CommandMethod("SyncBlocks")]
public void SyncBlocks()
{
    var doc = Application.DocumentManager.MdiActiveDocument;
    var db = doc.Database;
    var ed = doc.Editor;
    ed.WriteMessage("\nSyncing block");

    ObjectId pickedId = ObjectId.Null;

    PromptEntityOptions opt = new PromptEntityOptions("Select block to sync");
    opt.AllowNone = false;

    // prompt user to select block reference
    PromptEntityResult res = ed.GetEntity(opt);
    if (res.Status != PromptStatus.OK)
    {
        ed.WriteMessage("\ninvalid selection");
    }
    pickedId = res.ObjectId;

    string blockName = ""; // will store selected block name
    // string filePath = db.Filename; // this was used for another method I was trying

    ObjectIdCollection btrIds = new ObjectIdCollection();

    using (var tx = db.TransactionManager.StartTransaction())
    {
        BlockReference br = tx.GetObject(pickedId, OpenMode.ForRead) as BlockReference;
        if (br == null)
        {
            ed.WriteMessage("\ninvalid selection");
            return;
        }
        blockName = br.Name;

        var btr = tx.GetObject(br.BlockTableRecord, OpenMode.ForRead) as BlockTableRecord;
        foreach (ObjectId id in btr)
        {
            btrIds.Add(id);
        }

        tx.Dispose();
    }

    ed.WriteMessage($"\nSelected \"{blockName}\"");

    OpenFileDialog ofd = new OpenFileDialog("Select DWGs to sync", null, "dwg", "DWG Files (*.dwg)|*.dwg", OpenFileDialog.OpenFileDialogFlags.AllowMultiple);

    // prompt user to select drawing files
    var dr = ofd.ShowDialog();
    if (dr != System.Windows.Forms.DialogResult.OK)
    {
        ed.WriteMessage("\nNo files selected");
        return;
    }

    var dm = Application.DocumentManager;

    // loop through each selected file
    foreach (var file in ofd.GetFilenames())
    {
        try
        {
            using(var tempDb = new Database(false, true))
            {
                // this is the database the block needs to be updated in
                tempDb.ReadDwgFile(file, FileOpenMode.OpenForReadAndWriteNoShare, true, null);
                tempDb.CloseInput(true);
                HostApplicationServices.WorkingDatabase = tempDb;

                using (var tx = tempDb.TransactionManager.StartTransaction())
                {
                    var bt = tx.GetObject(tempDb.BlockTableId, OpenMode.ForRead) as BlockTable;

                    var btr = tx.GetObject(bt[blockName], OpenMode.ForWrite) as BlockTableRecord;
                    if (btr == null)
                    {
                        dm.MdiActiveDocument.Editor.WriteMessage($"Block not found in \"{file}\"");
                        tx.Dispose();
                        continue;
                    }

                    foreach (ObjectId id in btr)
                    {
                        var ent = tx.GetObject(id, OpenMode.ForWrite) as Entity;
                        if (ent == null) continue;
                        ent.Erase();
                    }

                    var mapping = new IdMapping();

                    db.WblockCloneObjects(btrIds, btr.Id, mapping, DuplicateRecordCloning.Replace, false);

                    tx.Commit();
                }

                tempDb.SaveAs(file, DwgVersion.Current);
            }
        }
        catch (System.Exception ex)
        {
            dm.MdiActiveDocument.Editor.WriteMessage($"\nError syncing block in \"{file}\": {ex}");
        }
    }
}
0 Likes