Author Topic: Renaming a single instance of a block .. is it possible?  (Read 867 times)

0 Members and 1 Guest are viewing this topic.

Keith™

  • Villiage Idiot
  • Seagull
  • Posts: 16618
  • Superior Stupidity at its best
Re: Renaming a single instance of a block .. is it possible?
« Reply #15 on: June 09, 2017, 10:58:20 pm »
I'm starting to develop a direction of how I think this would be best handled. How does this sound for a beginning ...
1) Copy the layout and existing block references to a new layout;
2) Get a selection set of all block references;
3) For each block reference
    a) Get the base block type, there will ultimately be 8 to 10, from the block definition name (for argument sake, we'll call it "a");
    b) Store a copy of the attribute values in a predefined data class (this is already in use in another part of the program);
    c) Create a new block definition using the predefined block "a" and rename it to "a-layoutname-n";
    d) Create a block reference for the new block definition at the insertion point of the original block and with the same scale and rotation;
    e) Populate the attributes of "a-layoutname-n" with the stored attribute values;
    f) Fire the event handler (already defined) to do the magic with the new block reference;
    g) Wash, rinse and repeat;
4) Delete the original block definition;

I'm thinking this is probably the shortest way to get from point A to point B ....
Proud provider of opinion and arrogance since November 22, 2003 at 09:35:31 am
CadJockey Militia Field Marshal

Keith™

  • Villiage Idiot
  • Seagull
  • Posts: 16618
  • Superior Stupidity at its best
Re: Renaming a single instance of a block .. is it possible?
« Reply #16 on: June 09, 2017, 11:46:01 pm »
When I started looking at the methods available to me, this is what I came up with ... in my preliminary tests everything works as it should. The attributes remain as they should.

I'll have to do more in depth testing, but this *seems* to be the simplest solution.

I welcome any thoughts on the approach. Hopefully this kind of explains what I was talking about.

Code - C#: [Select]
  1.        private void RenameFromOldLayout(string OldLayout, string NewLayout)
  2.        {
  3.            //prepare acad vars
  4.            Document acDoc = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
  5.            Database acDB = acDoc.Database;
  6.            LayoutManager acLayoutMgr = LayoutManager.Current;
  7.            ObjectId acLO = acLayoutMgr.GetLayoutId(NewLayout);
  8.            //lock the document
  9.            using (acDoc.LockDocument())
  10.            {
  11.                using (Transaction acTrans = acDoc.TransactionManager.StartTransaction())
  12.                {
  13.                     //filter for inserts in the new layout
  14.                    TypedValue[] acTypValAr = new TypedValue[] {
  15.                    new TypedValue((int)DxfCode.Operator, "<and"),
  16.                    new TypedValue(0, "INSERT"),
  17.                    new TypedValue(410, NewLayout),
  18.                    new TypedValue((int)DxfCode.Operator, "and>")
  19.                    };
  20.                     //select inserts
  21.                    SelectionFilter renameItemsFilter = new SelectionFilter(acTypValAr);
  22.                    PromptSelectionResult SSet = acDoc.Editor.SelectAll(renameItemsFilter);
  23.                    if (SSet.Status == PromptStatus.OK)
  24.                    {
  25.                        acTrans.GetObject(acLO, OpenMode.ForWrite);
  26.                        //loop through all selected block references
  27.                        foreach (SelectedObject ent in SSet.Value)
  28.                        {
  29.                            //get the block table
  30.                            BlockTable bt = (BlockTable)acTrans.GetObject(acDB.BlockTableId, OpenMode.ForRead);
  31.                            //get the current block reference open for WRITE because we will be modifying it later
  32.                            BlockReference cEnt = (BlockReference)acTrans.GetObject(ent.ObjectId, OpenMode.ForWrite);
  33.                            //get the reference's definition
  34.                            BlockTableRecord cDef = (BlockTableRecord)acTrans.GetObject(bt[cEnt.Name], OpenMode.ForRead);
  35.                            //create a new db with a copy of the current block definition
  36.                            Database tempDB = acDB.Wblock(cDef.ObjectId);
  37.                            ObjectId copyID = ObjectId.Null;
  38.                            using (tempDB)
  39.                            {
  40.                                //add it to the current drawing's database as the new name replacing the old layout name with the new layout name
  41.                                copyID = acDB.Insert(cEnt.Name.Replace(OldLayout, NewLayout), tempDB, true);
  42.                            }
  43.                            //change the BlockTableRecord to point to the new block definition
  44.                            cEnt.BlockTableRecord = copyID;
  45.                        }
  46.                    }
  47.                    acTrans.Commit();
  48.                }
  49.                acDoc.Editor.Regen();
  50.            }
  51.        }
Proud provider of opinion and arrogance since November 22, 2003 at 09:35:31 am
CadJockey Militia Field Marshal

n.yuan

  • Bull Frog
  • Posts: 235
Re: Renaming a single instance of a block .. is it possible?
« Reply #17 on: June 10, 2017, 02:45:16 pm »
Well, your code looks simpler than the steps you described. Obviously the code only does part of the steps in your description.

I also wrote some code doing the process as you originally described and as suggested in my previous reply:

1. Copy content from a layout ("Layout1", the content is only a viewport and a blockreference that is in interest) to a few new layouts
2. Create a set of new block definitions, which are exactly the same as the original block definition of the existing block reference on "Layout1", except for a new name.
3. Redirect the block references on each layout (references to the original block definition) to be reference of new block definition with name corresponding to the layout name.

The net result is that each layout has a block reference and all the block references on all layout look the same, but have different block name.

Here is the code:
Code: [Select]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;

[assembly: CommandClass(typeof(ChangeBlockDefinition.Commands))]

namespace ChangeBlockDefinition
{
    public class Commands
    {
        [CommandMethod("DoWork")]
        public static void RunMyCommand()
        {
            var doc = CadApp.DocumentManager.MdiActiveDocument;
            var ed = doc.Editor;

            string originalBlock = "Original";

            try
            {
                //Copy content from "Layout1" to new layouts
                var layoutNames = CreateLayouts();
                //Create new block definitions corresponding to each layout
                var newBlockDefIds =
                    DefineBlockForEachLayout(doc, originalBlock, layoutNames);
                //Update BlockReference on each layout to be BlockRefernce of
                //newly created block definition
                UpdateBlockReferenceOnLayouts(doc, newBlockDefIds);
            }
            catch (System.Exception ex)
            {
                ed.WriteMessage("\nError\n{0}", ex.Message);
                ed.WriteMessage("\n*Cancel*");
            }
            finally
            {
                Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt();
            }
        }

        // Assume the drawing has one layout ("Layout1" with one block reference, named as
        // 'Original" inserted at (0.0, 0.0, 0.0), which has a few attributes.
        // Using LayoutManager.CopyLayout() to create a few more layouts, named as
        // "Layout2", "Layout3"...
        private static IEnumerable<string> CreateLayouts()
        {
            var layoutNames = new List<string>();

            string sourceLayout = "Layout1";

            layoutNames.Add(sourceLayout);

            for (int i=2; i<=5; i++)
            {
                string newLayout = "Layout" + i;
                layoutNames.Add(newLayout);

                LayoutManager.Current.CopyLayout(sourceLayout, newLayout);
            }

            LayoutManager.Current.CurrentLayout = sourceLayout;

            return layoutNames;
        }

        // Create new block definitions, which is the same as the original block defnition
        // except for it name
        private static Dictionary<string, ObjectId> DefineBlockForEachLayout(
            Document dwg, string sourceBlkName, IEnumerable<string> layoutNames)
        {
            var dic = new Dictionary<string, ObjectId>();

            using (var tran = dwg.TransactionManager.StartTransaction())
            {
                var blkTable = (BlockTable)tran.GetObject(dwg.Database.BlockTableId, OpenMode.ForRead);
                if (blkTable.Has(sourceBlkName))
                {
                    var sourceBlkId = blkTable[sourceBlkName];
                    foreach (var layout in layoutNames)
                    {
                        var blkName = sourceBlkName + "_" + layout;
                        var newBlkId = CreateNewBlockDefinition(dwg.Database, sourceBlkId, blkName);
                       

                        dic.Add(layout, newBlkId);
                    }
                }
                tran.Commit();
            }

            return dic;
        }

        private static ObjectId CreateNewBlockDefinition(
            Database sourceDB, ObjectId sourceBlkId, string blkName)
        {
            ObjectId blkId = ObjectId.Null;

            using (var blkDb = sourceDB.Wblock(sourceBlkId))
            {
                blkId = sourceDB.Insert(blkName, blkDb, true);
            }

            return blkId;
        }

        private static void UpdateBlockReferenceOnLayouts(
            Document dwg, Dictionary<string, ObjectId> layoutBlockDefIds)
        {
            bool regen = false;

            using (var tran = dwg.TransactionManager.StartTransaction())
            {
                foreach (var item in layoutBlockDefIds)
                {
                    var brefIds = GetBlockReferencesOnLayout(dwg.Editor, item.Key);
                    if (brefIds!=null)
                    {
                        foreach (var blkId in brefIds)
                        {
                            var blk = (BlockReference)tran.GetObject(blkId, OpenMode.ForRead);
                            if (blk.Name.ToUpper()=="ORIGINAL")
                            {
                                blk.UpgradeOpen();
                                //Set the BlockReference to be a reference to a new block definition
                                blk.BlockTableRecord = item.Value;

                                //Update attributes in the block reference here, if necessary
                                // ... ...

                                if (!regen) regen = true;
                            }
                        }
                    }
                }

                tran.Commit();
            }

            if (regen) dwg.Editor.Regen();
        }

        private static ObjectId[] GetBlockReferencesOnLayout(Editor ed, string layout)
        {
            var vals = new TypedValue[]
            {
                new TypedValue((int)DxfCode.Start,"INSERT"),
                new TypedValue((int)DxfCode.LayoutName, layout)
            };

            var res = ed.SelectAll(new SelectionFilter(vals));
            if (res.Status== PromptStatus.OK)
            {
                return res.Value.GetObjectIds();
            }
            else
            {
                return null;
            }
        }
    }
}

Here is a video clip showing the result of running this code:

https://www.screencast.com/t/KpDIQ8cO3


« Last Edit: June 10, 2017, 02:48:54 pm by n.yuan »

Keith™

  • Villiage Idiot
  • Seagull
  • Posts: 16618
  • Superior Stupidity at its best
Re: Renaming a single instance of a block .. is it possible?
« Reply #18 on: June 11, 2017, 07:48:58 am »
Well, your code looks simpler than the steps you described. Obviously the code only does part of the steps in your description.

You are correct. Other code already manages the copying of the layout and all of the related geometry. The goal was to have unique block names on that copied layout. As for the capturing of attributes, I found that by simply changing the BlockTableRecord on the BlockReference, the attributes remain exactly as they were in the original block, which is what I needed to do because the software will change the attributes separately based on other user input.

Proud provider of opinion and arrogance since November 22, 2003 at 09:35:31 am
CadJockey Militia Field Marshal

WILL HATCH

  • Bull Frog
  • Posts: 403
Re: Renaming a single instance of a block .. is it possible?
« Reply #19 on: June 19, 2017, 02:50:40 pm »
I'm starting to develop a direction of how I think this would be best handled. How does this sound for a beginning ...
1) Copy the layout and existing block references to a new layout;
2) Get a selection set of all block references;
3) For each block reference
    a) Get the base block type, there will ultimately be 8 to 10, from the block definition name (for argument sake, we'll call it "a");
    b) Store a copy of the attribute values in a predefined data class (this is already in use in another part of the program);
    c) Create a new block definition using the predefined block "a" and rename it to "a-layoutname-n";
    d) Create a block reference for the new block definition at the insertion point of the original block and with the same scale and rotation;
    e) Populate the attributes of "a-layoutname-n" with the stored attribute values;
    f) Fire the event handler (already defined) to do the magic with the new block reference;
    g) Wash, rinse and repeat;
4) Delete the original block definition;

I'm thinking this is probably the shortest way to get from point A to point B ....

I know my $0.02 rounds down in Canada now but here it is:
If you copy the entire layout and all the blocks you're going to be creating a bunch of new items in the new layout's BTR just to immediately delete them. I would recommend a bit of reflow to the approach here, that you identify your target layout, create the new blank layout, then iterate all the objects in the source layout while creating the new block definitions and references accordingly in the target layout properly on the first pass.

Keith™

  • Villiage Idiot
  • Seagull
  • Posts: 16618
  • Superior Stupidity at its best
Re: Renaming a single instance of a block .. is it possible?
« Reply #20 on: June 19, 2017, 09:51:58 pm »
I initially considered doing that, but in the end it was a lot more code to do just that. I still need to keep the rest of the geometry, viewports, etc. Copying the layout with this ...

Code - C#: [Select]
  1. acLayoutMgr.CopyLayout(source, target);

 ... copies everything, graphical and non-graphical, which is exactly what I need.
Proud provider of opinion and arrogance since November 22, 2003 at 09:35:31 am
CadJockey Militia Field Marshal

WILL HATCH

  • Bull Frog
  • Posts: 403
Re: Renaming a single instance of a block .. is it possible?
« Reply #21 on: June 21, 2017, 05:33:32 am »
On that line, I was thinking this could be solved efficiently with a DeepClone Overrule but I'm experiencing an odd result in this. It is creating two items in the same BlockTableRecord with the same id and the drawing misbehaves until doing an audit (even with selecting no to fixing errors...)
have attached before and after dwg for reference

I created a drawing with a block called MyBlock that had an attribute and placed it in a layout, this overrule causes us to insert a copy of that block, MyBlock1, in its place during the cloning
Code - C#: [Select]
  1.    public class DeepCloneOverrule : ObjectOverrule<BlockReference>
  2.    {
  3.        public static readonly DeepCloneOverrule Instance = new DeepCloneOverrule();
  4.        internal static HashSet<ObjectId> idsToChange { get; set; }
  5.        private DeepCloneOverrule()
  6.            : base(OverruleStatus.On)
  7.        {
  8.        }
  9.        public override DBObject DeepClone(DBObject dbObject, DBObject ownerObject, IdMapping idMap, bool isPrimary)
  10.        {
  11.            try
  12.            {
  13.                Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
  14.                if (idsToChange.Contains(dbObject.Id))
  15.                {
  16.                    var db = dbObject.Database;
  17.                    using (var tr = db.TransactionManager.StartTransaction())
  18.                    {
  19.                        var bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
  20.                        BlockTableRecord mb1r;
  21.                        if (bt.Has("MyBlock1"))
  22.                            mb1r = (BlockTableRecord)tr.GetObject(bt["MyBlock1"], OpenMode.ForRead);
  23.                        else
  24.                        {
  25.                            var mbr = (BlockTableRecord)tr.GetObject(bt["MyBlock"], OpenMode.ForRead);
  26.                            var col = new ObjectIdCollection();
  27.                            foreach (ObjectId id in mbr)
  28.                                col.Add(id);
  29.                            var map = new IdMapping();
  30.                            mb1r = new BlockTableRecord();
  31.                            mb1r.Name = "MyBlock1";
  32.                            bt.UpgradeOpen();
  33.                            bt.Add(mb1r);
  34.                            tr.AddNewlyCreatedDBObject(mb1r, true);
  35.                            db.DeepCloneObjects(col, mb1r.Id, map, false);
  36.                        }
  37.                        var mb = (BlockReference)dbObject;
  38.                        var mb1 = new BlockReference(Point3d.Origin, mb1r.Id);
  39.                        var owner = (BlockTableRecord)ownerObject;
  40.                        if (!owner.IsWriteEnabled)
  41.                            owner.UpgradeOpen();
  42.                        owner.AppendEntity(mb1);
  43.  
  44.                        tr.AddNewlyCreatedDBObject(mb1, true);
  45.                        mb1.GenerateAttibutes();
  46.                        mb1.TransformBy(mb.BlockTransform);
  47.                        var dict = new Dictionary<string, AttributeReference>();
  48.                        foreach (ObjectId attId in mb1.AttributeCollection)
  49.                        {
  50.                            var ar = (AttributeReference)tr.GetObject(attId, OpenMode.ForWrite);
  51.                            dict.Add(ar.Tag, ar);
  52.                        }
  53.  
  54.                        foreach (ObjectId attId in mb.AttributeCollection)
  55.                        {
  56.                            var ar = (AttributeReference)tr.GetObject(attId, OpenMode.ForRead);
  57.                            dict[ar.Tag].TextString = ar.TextString;
  58.                        }
  59.                        idMap.Add(new IdPair(mb.ObjectId, mb1.ObjectId, false, isPrimary, true));
  60.                        tr.Commit();
  61.                        ed.WriteMessage("\nAll done");
  62.                        return mb1;
  63.                    }
  64.                }
  65.  
  66.                return base.DeepClone(dbObject, ownerObject, idMap, isPrimary);
  67.            }
  68.            catch (System.Exception e)
  69.            {
  70.                Application.ShowAlertDialog(string.Format("Error:\n{0}", e.ToString()));
  71.                return base.DeepClone(dbObject, ownerObject, idMap, isPrimary);
  72.            }
  73.        }
  74.  
  75.  
  76.  
  77.    }
  78.    ///Based on code from:
  79.    ///https://www.theswamp.org/index.php?topic=45662.msg508188#msg508188
  80.    ///Thank You Jeff H!
  81.  
  82.    public enum OverruleStatus
  83.    {
  84.        Off = 0,
  85.        On = 1
  86.    }
  87.  
  88.  
  89.    public abstract class ObjectOverrule<T> : ObjectOverrule where T : DBObject
  90.    {
  91.        private readonly RXClass _targetClass = RXObject.GetClass(typeof(T));
  92.        OverruleStatus _status = OverruleStatus.Off;
  93.        public OverruleStatus Status
  94.        {
  95.            get
  96.            {
  97.                return _status;
  98.            }
  99.            set
  100.            {
  101.                if (value == OverruleStatus.On && _status == OverruleStatus.Off)
  102.                {
  103.                    AddOverrule(_targetClass, this, true);
  104.                    _status = OverruleStatus.On;
  105.                }
  106.                else if (value == OverruleStatus.Off && _status == OverruleStatus.On)
  107.                {
  108.                    RemoveOverrule(_targetClass, this);
  109.                    _status = OverruleStatus.Off;
  110.                }
  111.            }
  112.        }
  113.        protected ObjectOverrule(OverruleStatus status = OverruleStatus.On)
  114.        {
  115.            Status = status;
  116.        }
  117.    }

And can be called like
Code - C#: [Select]
  1. var layout = (Layout)tr.GetObject(layoutDict.GetAt(copyName), OpenMode.ForRead);
  2.                var lbtr = (BlockTableRecord)tr.GetObject(layout.BlockTableRecordId, OpenMode.ForRead);
  3.                HashSet<ObjectId> idsToChange = new HashSet<ObjectId>();
  4.                foreach (ObjectId id in lbtr)
  5.                {
  6.                    if (id.ObjectClass == RXClass.GetClass(typeof(BlockReference)))
  7.                    {
  8.                        var br = (BlockReference)tr.GetObject(id, OpenMode.ForRead);
  9.                        if (br.Name == "MyBlock")
  10.                        {
  11.                            idsToChange.Add(id);
  12.                        }
  13.                    }
  14.                }
  15.                DeepCloneOverrule.idsToChange = idsToChange;
  16.                DeepCloneOverrule.Instance.Status = OverruleStatus.On;
  17.                layoutMgr.CopyLayout(copyName, newName);
  18.                layoutMgr.CurrentLayout = newName;
  19.                DeepCloneOverrule.Instance.Status = OverruleStatus.Off;
  20.  
you will need this extension
Code - C#: [Select]
  1.        public static void GenerateAttibutes(this BlockReference br)
  2.        {
  3.            using (Transaction tr = br.Database.TransactionManager.StartTransaction())
  4.            {
  5.                bool wasDown = !br.IsWriteEnabled;
  6.                if (wasDown) br.UpgradeOpen();
  7.                using (BlockTableRecord btr = (BlockTableRecord)br.BlockTableRecord.GetObject(OpenMode.ForRead))
  8.                {
  9.                    foreach (ObjectId id in btr)
  10.                    {
  11.                        if (id.ObjectClass == RXClass.GetClass(typeof(AttributeDefinition)))
  12.                        {
  13.                            AttributeDefinition ad = (AttributeDefinition)id.GetObject(OpenMode.ForRead);
  14.                            if (ad.Constant) continue;
  15.                            AttributeReference ar = new AttributeReference();
  16.                            ar.SetDatabaseDefaults();
  17.                            ar.SetAttributeFromBlock(ad, br.BlockTransform);
  18.                            br.AttributeCollection.AppendAttribute(ar);
  19.                            tr.AddNewlyCreatedDBObject(ar, true);
  20.                        }
  21.                    }
  22.                }
  23.                if (wasDown) br.DowngradeOpen();
  24.                tr.Commit();
  25.            }
  26.        }