.NET
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Cloning of dynamic blocks

12 REPLIES 12
Reply
Message 1 of 13
max.senft
3025 Views, 12 Replies

Cloning of dynamic blocks

Hi everybody.

 

I'm currently struggling with cloning dynamic blocks inside a single document. Here's what I've got:

 

  • The user inserts a dynamic block using the AutoCAD command "_insert". So far, a block reference is created by AutoCAD pointing to the "original" BlockTableRecord of the dynamic block (called "MyDynamicBlock", for example).
  • The user changes the dynamic block (for example changing a linear parameter). Now AutoCAD automagically creates a copy of the original block definition, gives it an anonymous name (for example "*U1") and updates (or replaces?) the BlockReference so it now points to this anonymous copy.
  • The user copies this changed dynamic block using the AutoCAD command "_copy". As far as I analyzed it correctly, AutoCAD now creates new BlockReferences all pointing to the same anonymous block definition "*U1".
  • If the user again changes some "dynamic parameters", AutoCAD once again automagically creates a new anonymous block definition (for exmaple "*U2") and updates/replaces the BlockReference.

So far, typical AutoCAD behaviour. The problem in my case is, that the dynamic block has nested blocks. These nested blocks have attributes or other kind of data/elements that need to be changed. I've already written methods to dive into the structure to get to the data and change these. But since all "instances" of the original dynamic block (either the real original or any anonymified copies) point to the same nested BlockTableRecord, the attributes will change for all instances.

 

I also already created a method that will do "recursive deep copy" of a BlockTableRecord, which works perfectly as long as the block is not dynamic. If it is dynamic it somehow looses the information, that it originally was has dynamic behaviour.

 

I did even more analysis on this topic using ArxDbg and some "personal" methods to get to the "low level information" of the database. I found out that the information is stored as XData in the BlockTableRecords. The following image shows the original block definiton (in my text above it would be the "MyDynamicBlock" BlockTableRecord):

 

dynblock-original.png

 

So, there is a "AcDbBlockRepETag", "AcDbDynamicBlockTrueName" and a "AcDbDynamicBlockGUID" entry containting some information.

 

The following image shows the data of a dynamic block (for example the "*U1" BlockTableRecord) with changed dynamic parameters:

 

dynblock-changed.png

 

This block now only has a single XData "AcDbBlockRepBTag" with an "ExtendedDataHandle" (value 93) which is the handle of the original dynamic BlockTableRecord. So I guess this is the link between the changed dynamic block and the original one. Unfortunately I found absolutely NO information about this, which drives me almost crazy.

 

Nonetheless, for the first step, I tried to copy a changed dynamic block to a new Database and store this to a new file, since I guess I have to use a temporary database in any case... here's the code I'm currently using:

 

            // selId is the ObjectId of the block to copy and was retrieved earlier in the code!
using (var tr = doc.TransactionManager.StartTransaction()) { var br = tr.GetObject((ObjectId)selId, OpenMode.ForRead) as BlockReference; if (br == null) { return; } var btr = tr.GetObject(br.BlockTableRecord, OpenMode.ForRead) as BlockTableRecord; if (btr == null) { return; } using (Database tmpDb = new Database(true, true)) { ObjectIdCollection blockIds = new ObjectIdCollection(); blockIds.Add(br.BlockTableRecord); if (br.IsDynamicBlock) { blockIds.Add(br.DynamicBlockTableRecord); } IdMapping mapping = new IdMapping(); db.WblockCloneObjects(blockIds, tmpDb.BlockTableId, mapping, DuplicateRecordCloning.Ignore, false); using (var tmpTr = tmpDb.TransactionManager.StartTransaction()) { var tmpBt = tmpTr.GetObject(tmpDb.BlockTableId, OpenMode.ForRead) as BlockTable; if (tmpBt == null) { return; } var storedBTR = tmpTr.GetObject(mapping[br.BlockTableRecord].Value, OpenMode.ForRead) as BlockTableRecord; if (storedBTR == null) { return; } var newBR = new BlockReference(Point3d.Origin, storedBTR.ObjectId); var newMS = (BlockTableRecord)tmpTr.GetObject(tmpBt[BlockTableRecord.ModelSpace], OpenMode.ForWrite); newMS.AppendEntity(newBR); tmpTr.AddNewlyCreatedDBObject(newBR, true); tmpTr.Commit(); } var curDialog = new SaveFileDialog(); curDialog.AddExtension = true; curDialog.Filter = "DWG-Dateien (*.dwg)|*.dwg"; if (curDialog.ShowDialog() == DialogResult.OK) { // Es soll tatsaechlich eine Datei gespeichert werden tmpDb.SaveAs(curDialog.FileName, DwgVersion.Current); WriteToEditorOut(ed, "Datei gespeichert unter \"" + curDialog.FileName + "\"."); } } tr.Commit(); }

 So, I used the WblockCloneObjects method using a ObjectIdCollection containing the user-selected BlockReference and the DynamicBlockTableRecord of the reference. When I now open the saved file, I see the copied block but without any dynamic behaviour. On the other side, the XData entries are still there, and even the handle has been updated! Which tiny bit am I missing? How does AutoCAD get the knowledge of the dynamic behaviour?

 

The final step of my needed work is, that I have to rename the block clones to "*U" so that no copy will overlap with another at any time. Is this a proper solution for this? Or should I better give unique identifiers by any other way (like create GUIDs using the .Net possibilities)?

 

I know this is a hard topic, but I hope I get some answer to any part of my problem!

 

Thanks

Max

12 REPLIES 12
Message 2 of 13
_gile
in reply to: max.senft

Hi,

 

What about only cloning the block reference, this will import both its BlockTableRecord and its DynamicBlockTableRecord.

 

var ids = new ObjectIdCollection();
ids.Add(selId);
using (var targetDb = new Database())
{
    targetDb.ReadDwgFile(filename, System.IO.FileShare.ReadWrite, false, "");
    var msId = SymbolUtilityServices.GetBlockModelSpaceId(targetDb);
    var idmap = new IdMapping();
    db.WblockCloneObjects(ids, msId, idmap, DuplicateRecordCloning.Replace, false);
    targetDb.SaveAs(filename, DwgVersion.Current);
}

 



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 3 of 13
max.senft
in reply to: _gile

Hi!

 

Hmhm, I'm not sure if my explanation of the problem is misunderstandable. I've got one document in which I need to do the cloning. Saving the clone to a new file was just for test reasons!

 

Again: I've got a definition of a dynamic block that contains nested blocks. These nested blocks contain attributes I need to change during creation of the drawing. The problem is, that each reference will point to the same "outer block definition". This contains a reference to a block containing attributes, so the attribute values are stored in this reference. Now I have to be able to set different values to these attributes. As far as I understood, I have to change the block reference inside the "outer block definition"!? By that means, if I only have one level of nested blocks, I have to clone the outer block definition and change the reference inside this close, correct? So, for non-dynamic blocks, this is absolutely no problem. It is also no problem if non of the dynamic block parameters have changed. But if any parameter has changed, the clone looses its dynamic feature (gets static).

 

I hope the problem is more clear now?

 

Best regards

Max

Message 4 of 13
_gile
in reply to: max.senft

Could you upload a block example ?



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 5 of 13
max.senft
in reply to: _gile

Hi!

 

Of course. The file contains the "MyDynamicBlock" block definition which contains a reference to "Block1". This "Block1" has attributes defined. The drawing contains an unchanged "instance" of the MyDynamicBlock and a changed "instance". Now, I need to be able to change the "inner attributes" individually for each instance.

 

Regards

Max

Message 6 of 13
Mikko
in reply to: max.senft

had a look at your MyDynamicBlock, looks like you have two block inside it and I was just wondering why you didn't just make the circle and attributes all one in the same as the dynamic parts. nothing wrong that I see but your problem would be easier to solve had you done that I think, but I'm getting old and slow so I might be wrong.
Message 7 of 13
max.senft
in reply to: Mikko

Hi,

 

yeah, ok, not the best example file, sorry. It is a simplified block to show the problem. I may not upload the original file. But I created a new file (see attachment) according to the following explanations.

 

You now have a "plain" dynamic block "MechanicalPart1" which is the base for the "ElectroMechanicalPart1". The circle now is block "Sensor1". In the "ElectroMechaniclPart1" you have two sensors which have to have their own attributes (like name, type, IP-address whatever). So actually each sensor must have its own attribute values.

 

If I would now pull all the attributes up to the top level (the dynamic block) I would have no knowledge about what attribute belongs to which sensor.

 

That's the background why we need to use nested blocks. This way, the attributes are "physically" connected to the component.

 

Regards

Max

Message 8 of 13
_gile
in reply to: max.senft

Sorry for misunderstanding you.

 

It seems to be possible to change the attributes values in the nested block of each BlockTableRecord (MyDynamicBlock and *U2).

 

This snippet works for mewith the drawing sample you provided (attributes values are changed and the blocks remain dynamic).

 

        [CommandMethod("Test")]
        public void Test()
        {
            Document doc = AcAp.DocumentManager.MdiActiveDocument;
            Database db = doc.Database;
            Editor ed = doc.Editor;

            using (Transaction tr = db.TransactionManager.StartTransaction())
            {
                BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
                BlockTableRecord btr = (BlockTableRecord)tr.GetObject(bt["MyDynamicblock"], OpenMode.ForRead);
                ChangeAttributesValuesInNestedBlock(btr, "foo");
                btr = bt["*U2"].GetObject<BlockTableRecord>();
                ChangeAttributesValuesInNestedBlock(btr, "bar");
                tr.Commit();
            }
            ed.Regen();
        }

        private void ChangeAttributesValuesInNestedBlock(BlockTableRecord btr, string value)
        {
            BlockReference br = null;
            foreach (ObjectId id in btr)
            {
                if (id.ObjectClass == RXClass.GetClass(typeof(BlockReference)))
                {
                    br = (BlockReference)id.GetObject(OpenMode.ForWrite);
                    break;
                }
            }
            if (br == null)
                return;
            int cnt = 1;
            foreach (ObjectId id in br.AttributeCollection)
            {
                var att = (AttributeReference)id.GetObject(OpenMode.ForWrite);
                att.TextString = value + "_" + cnt++;
            }
        }

 



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 9 of 13
max.senft
in reply to: _gile

Hi,

 

thanks for the code snippet. The problem with this "easy" solution (which I already implemented) is, that if you "_copy" any of the block references, You will have the same attribute values in all copies. That's why I'm looking for a smart way to do the cloning.

 

Regards

Max

Message 10 of 13
_gile
in reply to: max.senft

As far as I know, while two block references refer to same block definition, they contains exactly the same entities (except first level attribute references), only the geometry can be different (location, rotation, scales).

So, the only way I see would be to create a new block definition after the copy by changing a dynamic property so that you can edit this new block definition.

 

Another way would be to have several visual states in the dynamic block with different values for the nested block references attributes.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 11 of 13
max.senft
in reply to: _gile

Hi everybody,

 

I thought I will give you one more "final update". I'm sad, that there is no API functionality to support the required copy/cloning.

 

Anyway, changing a dynamic property seems to be the only way to get a copy of the original dynamic block the proper way (so it get's its own *U BlockTableRecord which can be changed). For that, I realized that simply setting one dynamic property with the same value that it already has also triggers AutoCAD to produce a *U block. Later on I had to go one step further, cause AutoCAD again seems to check the properties over all *U blocks of the same "dynamic block type" to keep the amount of block definition small. Darn I thought, AutoCAD thinks too much for me at the wrong place. 😉

 

So, my final solution (which is really not as nice as I wanted it) is, that I added a "Block Properties Table" to my dynamic blocks and added a textual user parameter called "myGUID". During my command action I simply set a newly generated Guid to this property and poof! AutoCAD automagically creates a new *U BlockTableRecord if the current BlockTableRecord is used by multiple BlockReferences.

 

I currently cannot say if there are dark sides on this solution, but for now it works as I needed. Copying the BlockTableRecord for a static block is easy on the other side.

 

Thanks to all that contributed to this topic (and may contribute in the future 🙂 ).

 

Regards

Max

Message 12 of 13
sonny3g
in reply to: max.senft

I need to know how you did this as I have a similar problem with several hundred dynamic blocks.  We insert our dynamic blocks and sometimes copying and pasting them, then make minor changes using a dynamic property, ie change the width or height of the block.  Right now, on the blocks that have both dynamic properties and constraints, when we do multiple inserts or copy and paste, then change any property parameter, it changes all instances of that block.

 

So far, my only solution has been to automatically rename the block with the date/time suffixed to the original name, immediately after inserting the block.  However, that does not solve the problem when a user makes copies of that block and changes a property.  All copies and the copied block change and will not reset with an undo.

 

If there is some way to get around this problem, other than "don't use constraints", I would really like to hear about it.

 

Thanks.

 

 

Scott G. Sawdy
scott.sawdy@bluecoyotecad.com
Message 13 of 13
ActivistInvestor
in reply to: sonny3g

See the code in > this post < for a possible solution.

 


@sonny3g wrote:

I need to know how you did this as I have a similar problem with several hundred dynamic blocks.  We insert our dynamic blocks and sometimes copying and pasting them, then make minor changes using a dynamic property, ie change the width or height of the block.  Right now, on the blocks that have both dynamic properties and constraints, when we do multiple inserts or copy and paste, then change any property parameter, it changes all instances of that block.

 

So far, my only solution has been to automatically rename the block with the date/time suffixed to the original name, immediately after inserting the block.  However, that does not solve the problem when a user makes copies of that block and changes a property.  All copies and the copied block change and will not reset with an undo.

 

If there is some way to get around this problem, other than "don't use constraints", I would really like to hear about it.

 

Thanks.

 

 


 

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Autodesk DevCon in Munich May 28-29th


Autodesk Design & Make Report

”Boost