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

0 Members and 1 Guest are viewing this topic.

Keith™

  • Villiage Idiot
  • Seagull
  • Posts: 16618
  • Superior Stupidity at its best
Renaming a single instance of a block .. is it possible?
« on: June 05, 2017, 07:12:58 pm »
I was going to write a dissertation on why this is necessary, but it was getting too long ... so here is the abbreviated version.

I am programmatically copying a layout to a new layout, including all geometry that is on the source layout. I want to rename the block references on the new layout because their definition will be modified programmatically and I don't want to affect the source blocks.

To complicate matters, each block may have between 2 and 67 attributes at various scales, locations and rotations within the source block. These will not be changing.

Consider this scenario:
Insert block "a" on a layout named "Current" then rename block "a" to "a-Current-n", where n is an index number;
Copy "Current" layout and rename it to "Future";
Rename all block references on "Future" layout to "a-Future-n" where n is the index number;

"a" will remain the same, n will remain the same, the only thing that changes is "Current" changes to "Future".

While I sit here and type this, I am considering if I can just clone the block defs and rename them, then rename the references to match.

Code not required, I'll address that later, a methodology is what I am looking for at this point ...
Proud provider of opinion and arrogance since November 22, 2003 at 09:35:31 am
CadJockey Militia Field Marshal

Atook

  • Swamp Rat
  • Posts: 842
Re: Renaming a single instance of a block .. is it possible?
« Reply #1 on: June 05, 2017, 07:50:26 pm »
Still trying to sort out what you're trying to do.

Will the geometry of the block be changing, or just  attribute values?

If the geometry is changing, it seems like you'll need a unique block for every layout. You'd need to modify the geometry and give it a unique name then insert it on that layout. I like your naming idea, then you can parse for it later if  you need to access programmatically .

There is however, a good chance that I'm misunderstanding your post completely.  :brow:

Keith™

  • Villiage Idiot
  • Seagull
  • Posts: 16618
  • Superior Stupidity at its best
Re: Renaming a single instance of a block .. is it possible?
« Reply #2 on: June 05, 2017, 10:18:31 pm »
I think you have the basic idea .. the geometry will be modified, similar to a dynamic block.

The crux of this project is that there are only 25 +/- blocks that can be configured in tens of thousands of ways to create a drawing. As the attributes change and are updated to various values, the blocks automatically reconfigure based on predefined parameters. Sort of like a dynamic block with parametric constraints that are tied to attribute values.

The user will select data points that are imported. Initially, there are no blocks in the drawing, but once a dataset has been imported, the user can then use that base model to generate other layouts and update the blocks on that particular layout with new data without changing the initial one.

So basically, the code create a block programmatically and gives it a unique name. It then inserts other blocks into it to create the geometry based on attribute values. If that block is copied, it needs to be renamed because two instances of the same block will cause data calculations to be incorrect.
Proud provider of opinion and arrogance since November 22, 2003 at 09:35:31 am
CadJockey Militia Field Marshal

Atook

  • Swamp Rat
  • Posts: 842
Re: Renaming a single instance of a block .. is it possible?
« Reply #3 on: June 05, 2017, 10:37:39 pm »
... If that block is copied, it needs to be renamed because two instances of the same block will cause data calculations to be incorrect.

This could be a sticking point. Would it be possible that a user copy this block while your software isn't running? If so, you'll have two instances of the same block and all the tomfoolery that goes along with it.

If not, you've laid out the problem pretty well. It's almost time to write some code for it. :)

Keith™

  • Villiage Idiot
  • Seagull
  • Posts: 16618
  • Superior Stupidity at its best
Re: Renaming a single instance of a block .. is it possible?
« Reply #4 on: June 05, 2017, 10:58:41 pm »
The software will be loaded automatically when AutoCAD opens. Of course no amount of code can control what people do to break drawings, but if they are given the tools to do it correctly to begin with, the chances of breaking the drawing is minimized somewhat.
Proud provider of opinion and arrogance since November 22, 2003 at 09:35:31 am
CadJockey Militia Field Marshal

n.yuan

  • Bull Frog
  • Posts: 236
Re: Renaming a single instance of a block .. is it possible?
« Reply #5 on: June 06, 2017, 09:37:04 am »
...
Consider this scenario:
Insert block "a" on a layout named "Current" then rename block "a" to "a-Current-n", where n is an index number;
Copy "Current" layout and rename it to "Future";
Rename all block references on "Future" layout to "a-Future-n" where n is the index number;

"a" will remain the same, n will remain the same, the only thing that changes is "Current" changes to "Future".
 ...

The problem I am having to understand your question is: you describe the scenario as CAD user, or as programmer? Since this a forum about programming, let's assume it is the latter. Then we programmer should always refer "block" by BlockTableRecord and/or BlockReference. When you say "insert block 'a'" and then "rename block 'a'", it is not clear "block 'a'" means BlockTableRecord", or "BlockReference": one can insert a block drawing as BlockTableRecord, and can also "insert" a BlockReference (technically, it is not insert, it is create, because the BlockTableRecord must exist/be inserted first). But BlockReference CANNOT be renamed, its takes the name of the BlockTableRecord. I am sure you know all this, but point it out for what I am trying to say as following;

Here is my brief translation of you want to do (with code)

  • On layout "Current", insert a block definition "a", then create a BlockReference to it with a lot attributes according to the "a" BlockTableRecord;
  • Copy everything (only one BlockReference, let's assume) on layout "Current" to layout "Future".
  • You want the BlockReference on layout Current" change its name to "a-xxx-index", and the one on Layout "Fututre" change its name to "a-yyyy-index" (or whatever name). However, the BlockTableRecord remains unchanged

As I mentioned above, BlockReference's name is not changeable (i.e. Read-only), so, the only way to do what you want is to create new BlockTableRecord with the wanted block name, and repoint a BlockReference's BlockTableRecord property to the new BlockTableRecord (i.e. the BlockReference is changed to be the reference of another BlockTableRecord).

So, you can do this:

1. Use code to create a new BlockTableRecord, which has the same geometries as BlockTableRecord "a", name it as needed.
2. set the target BlockReference's BlockTablerecord property to the newly created BlockTableRecord's ObjectId.
3. If the "renamed" BlockReference remains to be the only reference of the newly created BlockTableRecord (just for the naming purpose), you can omit creating all the AttributeDenitions in the new blocktablerecord as in block definition "a". That is, the newly created blocktablerecord not having all the attributedefinitions will not affect the existing BlockReference's attributereferences.
4. After you have all new blocktablerecords created and corresponding BlockReferences are RE-referenced, call Editor.Regen() to update the BlockReference view in editor.

Hopefully my understanding to your need is not off too much

Bryco

  • Water Moccasin
  • Posts: 1811
Re: Renaming a single instance of a block .. is it possible?
« Reply #6 on: June 06, 2017, 08:05:59 pm »
ditto
Programatically you can make the new btr using clone foe all the items in the existing btr,
then you can use the paperspace block to access the insertpts and rotations to insert the new brefs in.
So you are not copying the layout more like making a new layout and populating it.
This could involve more work if you have tricky viewports otherwise fairly simple

Keith™

  • Villiage Idiot
  • Seagull
  • Posts: 16618
  • Superior Stupidity at its best
Re: Renaming a single instance of a block .. is it possible?
« Reply #7 on: June 06, 2017, 11:22:54 pm »
Yeah, I can see where it is confusing. I'll try to explain it in the simplest terms possible from the programming perspective.

I programmatically copy a layout, including all of the associated geometry, to a new layout.
Code - C#: [Select]
  1. acLayoutMgr.CopyLayout(source, target);

The new target layout is an exact copy of the source layout with the exception of the name (and tab order which I set elsewhere)

The source layout has multiple block references, each with multiple attributes that get copied to the target layout. I need these block references to have a unique name that consists of the base block name (it is one of 12 predefined names), the layout name (where the block reference resides), and an index that identifies that block uniquely in that layout. Thus, if the source layout was named "Current" and the target layout was named "Future", a block reference named "a-current-32" in the "Current" layout would be "a-future-32" in the "Future" layout.

I realize that is going to require a new BlockDefinition in the BlockTableRecord because in the end, there can only be one block reference for every block definition.

What I am envisioning is the following:
Open a user form;
Copy a layout to a new layout;
Get a collection of all Block References on the target layout;
For each Block Reference in Future layout,
       Copy the existing Block Definition (i.e. "a-Current-21" ) to a new Block Definition ("a-Future-21");
       Update the associated Block Reference ("a-Current-21") to point to the new Block Definition ("a-Future-21");
Regen the editor;
Close the userform;

I guess a real simple way to explain it would be to say "Replace all inserts on a specified layout with new inserts that reference a copy of the original block definition, while replacing the layout name in the source block definition name with the layout name of the target layout."

I really hope this makes sense because I am not sure I know how to explain it any different.
Proud provider of opinion and arrogance since November 22, 2003 at 09:35:31 am
CadJockey Militia Field Marshal

Bryco

  • Water Moccasin
  • Posts: 1811
Re: Renaming a single instance of a block .. is it possible?
« Reply #8 on: June 07, 2017, 10:44:31 pm »
/yes so you only need 1 sub  replaceblockrefs(). And you are using the paperspace block belonging to the layout to cycle thru the blockfrefs. Using  a dictionary to copy the atts.

Keith™

  • Villiage Idiot
  • Seagull
  • Posts: 16618
  • Superior Stupidity at its best
Re: Renaming a single instance of a block .. is it possible?
« Reply #9 on: June 07, 2017, 11:54:35 pm »
/yes so you only need 1 sub  replaceblockrefs(). And you are using the paperspace block belonging to the layout to cycle thru the blockfrefs. Using  a dictionary to copy the atts.
No code as of yet .. coming to terms with the best way to manage doing it. Only then will I write some code, but it is getting close!
Proud provider of opinion and arrogance since November 22, 2003 at 09:35:31 am
CadJockey Militia Field Marshal

ronjonp

  • Needs a day job
  • Posts: 6403
Re: Renaming a single instance of a block .. is it possible?
« Reply #10 on: June 08, 2017, 11:44:30 am »
Do the blocks have to be named differently? Could you use Xdata instead? If the objects are on different tabs, it's very easy to grab all objects per tab.

Windows 10 x64 - AutoCAD /C3D 2018

Custom Build PC

Keith™

  • Villiage Idiot
  • Seagull
  • Posts: 16618
  • Superior Stupidity at its best
Re: Renaming a single instance of a block .. is it possible?
« Reply #11 on: June 08, 2017, 05:43:20 pm »
Do the blocks have to be named differently? Could you use Xdata instead? If the objects are on different tabs, it's very easy to grab all objects per tab.

Yes, they absolutely MUST be different blocks. No two block references to the same block definition should ever exist. The reason is that each block reference will have a varying number of attributes that will determine what the block ultimately looks like.

I can't divulge specifics, but it is kind of like a dynamic/parametric block.
Imagine you have a block definition that we'll call widget ... now that widget has some attributes that define its appearance ... lets imagine Widget has a "Shape" attribute that can have one of the following values: circle, square, triangle, hexagon, octagon; Widget might also have an attribute "Inscribed Diameter" that can be any positive real number ...
If we change the value of "Shape" to circle it will change to a circle, using "Inscribed Diameter" as the diameter of the circle. If we change the value of "Inscribed Diameter" to 10, the circle will resize to fit into a diameter of 10. If we then change the "Shape" attribute to "square" the widget will change to the shape of a square and will fit inside a circle with the diameter of 10. Because we are materially changing the objects within the block definition, it would affect all references equally.

Lets be clear, this is a significant dumbing down of the software in development. The previous example would be simple to do with a dynamic block, what I am doing, is leaps and bounds beyond dynamic blocks.
Proud provider of opinion and arrogance since November 22, 2003 at 09:35:31 am
CadJockey Militia Field Marshal

Keith Brown

  • Swamp Rat
  • Posts: 585
Re: Renaming a single instance of a block .. is it possible?
« Reply #12 on: June 08, 2017, 09:22:04 pm »
I might be late to the game and maybe have missed something but instead of copying a blockreference, can you just add the geometry to a new sided database and then add the side database as a new blocktablerecord and then add the blockreference?  That way you will not have to do any renaming or syncing?
Keith Brown | AutoCAD MEP Blog | RSS Feed
AutoCAD MEP 2014 / Revit MEP 2014 / EastCoast CAD/CAM addon / Visual Studio 2013

kdub

  • SuperMod
  • Swamp Rat
  • Posts: 982
  • class keyThumper<T>:ILazy<T>
Re: Renaming a single instance of a block .. is it possible?
« Reply #13 on: June 08, 2017, 10:47:00 pm »
@KeithBrown
Hi Keith,
Without knowledge of the ultimate goal I'm loath to suggest alternative structures.

Regarding the original isolated goal of duplicating blocks referenced in the Layout ...
   My initial thought was to make a side database for renaming and editing the relevant blocks, then insert the 'new' content into the layout copy, programmatically replacing the references.

    form follows function .... but  the form depends on info not available.

   
called Kerry in my other life

Sometimes the question is more important than the answer.

Keith Brown

  • Swamp Rat
  • Posts: 585
Re: Renaming a single instance of a block .. is it possible?
« Reply #14 on: June 09, 2017, 10:55:12 am »
@KeithBrown
Hi Keith,
Without knowledge of the ultimate goal I'm loath to suggest alternative structures.   


I agree Kerry, but since no code was written yet I thought I would throw it out there since it is not really changing the overall process. It was just a suggestion to keep from renaming a block and syncing attributes.
Keith Brown | AutoCAD MEP Blog | RSS Feed
AutoCAD MEP 2014 / Revit MEP 2014 / EastCoast CAD/CAM addon / Visual Studio 2013

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: 236
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: 405
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: 405
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.        }