Entity.DeepClone() vs. Database.DeepCloneObjects()

Entity.DeepClone() vs. Database.DeepCloneObjects()

norman.yuan
Mentor Mentor
4,316 Views
4 Replies
Message 1 of 5

Entity.DeepClone() vs. Database.DeepCloneObjects()

norman.yuan
Mentor
Mentor

Up until now, I have been using Database.WblockCloneObjects() to copy entities from other drawings, and using Database.DeepCloneObjects() to copy entities in the same drawing/database many times and never feld the need to call Entity.DeepClone() on individual entities.

 

In my current project, I run into the need to copy a single entity. Since when user decides to create a copy of the entity, it has already been opend in a trnasaction (as Entity), I though I'd simply call Entity.DeepClone() to create the copy, instead calling Database.DeepCloneObjects() as I am used to. I was surprised to find out the entity copy generated by Entity.DeepClone() does not show in AutoCAD editor, until the drawing is closed and re-opened in AutoCAD. Of course, when cloneing entity with Database.DeepCloneObjects(), this issue does not occur.

 

Here is the code (and video clip) to show the difference of the 2 cloning approaches:

 

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

[assembly: CommandClass(typeof(ODDataDeepClone.MyCommands))]

namespace ODDataDeepClone
{
    public class MyCommands
    {
        [CommandMethod("DoClone")]
        public static void RunMyCommand()
        {
            var doc = CadApp.DocumentManager.MdiActiveDocument;
            var ed = doc.Editor;

            ObjectId entId;
            Matrix3d transform;
            bool entClone;

            if (GetInputs(ed, out entId, out transform, out entClone))
            {
                if (entClone)
                {
                    DoEntityDeepClone(entId, transform);

                    // Following 2 lines of code have no effect
                    // to get DeepCloned entity showing
                    ////doc.TransactionManager.FlushGraphics();
                    ////ed.Regen();
                }
                else
                {
                    DoDatabaseDeepClone(entId, transform);
                }
            }
        }

        private static bool GetInputs(
            Editor ed,
            out ObjectId entId,
            out Matrix3d transform,
            out bool entDeepClone)
        {
            entId = ObjectId.Null;
            transform = new Matrix3d();
            entDeepClone = true;

            var eRes = ed.GetEntity("\nSelect an entity:");
            if (eRes.Status == PromptStatus.OK)
            {
                entId = eRes.ObjectId;
                var basePt = eRes.PickedPoint;

                var opt = new PromptPointOptions(
                    "\nSelect \"Move-To\" point:");
                opt.UseBasePoint = true;
                opt.BasePoint = basePt;

                var pRes = ed.GetPoint(opt);
                if (pRes.Status == PromptStatus.OK)
                {
                    transform = Matrix3d.Displacement(
                        basePt.GetVectorTo(pRes.Value));

                    var kOpt = new PromptKeywordOptions(
                        "\nEntity.DeepClone() or Database.DeepCloneObject()?");
                    kOpt.Keywords.Add("Entity");
                    kOpt.Keywords.Add("Database");
                    kOpt.Keywords.Default = "Entity";

                    var kRes = ed.GetKeywords(kOpt);
                    if (kRes.Status == PromptStatus.OK)
                    {
                        entDeepClone =
                            kRes.StringResult == "Entity" ? true : false;
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }
                else
                {
                    return false;
                }
            }
            else
            {
                return false;
            }
        }

        private static void DoEntityDeepClone(
            ObjectId entId, Matrix3d transform)
        {
            using (var tran =
                entId.Database.TransactionManager.StartTransaction())
            {
                var ent = (Entity)tran.GetObject(entId, OpenMode.ForRead);

                var owner = (BlockTableRecord)tran.GetObject(
                    ent.OwnerId, OpenMode.ForWrite);
                var mapping = new IdMapping();
                var cloned = (Entity)ent.DeepClone(owner, mapping, true);
                cloned.TransformBy(transform);
                tran.AddNewlyCreatedDBObject(cloned, true);
                tran.Commit();
            }
        }

        private static void DoDatabaseDeepClone(
            ObjectId entId, Matrix3d transform)
        {
            using (var tran =
                entId.Database.TransactionManager.StartTransaction())
            {
                var ent = (Entity)tran.GetObject(entId, OpenMode.ForRead);

                ObjectId ownerId = ent.OwnerId;
                var mapping = new IdMapping();
                ObjectIdCollection ids = new ObjectIdCollection(
                    new ObjectId[] { entId });
                entId.Database.DeepCloneObjects(ids, ownerId, mapping, false);
                foreach (IdPair pair in mapping)
                {
                    if (pair.IsCloned)
                    {
                        var cloned = tran.GetObject(
                            pair.Value, OpenMode.ForRead) as Entity;
                        if (cloned != null)
                        {
                            cloned.UpgradeOpen();
                            cloned.TransformBy(transform);
                        }
                    }
                }

                tran.Commit();
            }
        }
    }
}

I delibrately move the cloned entity away from its source, so the cloning can be visibly witnessed (well, in the case of calling Database.DeepCloneObjects().

 

I did a search and did not find any thing on this issue. Am I missing something? I tried the same code with AutoCAD 2017 and 2018, got the same result. Anyone knows on this?

 

As I said, I have been always used Database.DeepCloneObjects(), so, this issue is not clritical to me at all, but like to know if I missed something?

 

Norman Yuan

Drive CAD With Code

EESignature

4,317 Views
4 Replies
Replies (4)
Message 2 of 5

Anonymous
Not applicable

Editor.Regen or Editor.UpdateScreen doesn't cause the object to show up?

0 Likes
Message 3 of 5

ActivistInvestor
Mentor
Mentor

@norman.yuan wrote:

Up until now, I have been using Database.WblockCloneObjects() to copy entities from other drawings, and using Database.DeepCloneObjects() to copy entities in the same drawing/database many times and never feld the need to call Entity.DeepClone() on individual entities.

 

In my current project, I run into the need to copy a single entity. Since when user decides to create a copy of the entity, it has already been opend in a trnasaction (as Entity), I though I'd simply call Entity.DeepClone() to create the copy, instead calling Database.DeepCloneObjects() as I am used to. I was surprised to find out the entity copy generated by Entity.DeepClone() does not show in AutoCAD editor, until the drawing is closed and re-opened in AutoCAD. Of course, when cloneing entity with Database.DeepCloneObjects(), this issue does not occur.

 

Here is the code (and video clip) to show the difference of the 2 cloning approaches:

 

I delibrately move the cloned entity away from its source, so the cloning can be visibly witnessed (well, in the case of calling Database.DeepCloneObjects().

 

I did a search and did not find any thing on this issue. Am I missing something? I tried the same code with AutoCAD 2017 and 2018, got the same result. Anyone knows on this?

 

As I said, I have been always used Database.DeepCloneObjects(), so, this issue is not clritical to me at all, but like to know if I missed something

 

I can't give you a precise explanation why that happens, but DBObject.DeepClone() and Database.DeepCloneObjects() are not equivalent.

 

DBObject.DeepClone() (or its native counterpart) is called within the process performed by Database.DeepCloneObjects(), but the actual cloning of each object involved is just one step in a much more complicated process. You can read about it in the native ObjectARX developer's guide, but in a netshell, when an object is deep-cloned, any references it contains (in the form of ObjectIds) to other objects (or to itself) must be translated, and how that is done depends on the context of the cloning, and whether or not other objects that are referenced from an object that's being cloned are also being cloned.

 

And, consider that a DBObject can contain self-references, in something stored in its extension dictionary (that is, something in an object's extension dictionary can hold a reference to the object that owns the extension dictionary), so those references must also be translated, and that is something that cannot be done by a DBObject's implementation of DeepClone(), because the translation requires that the newly-created clone of a DBObject be added to a database and given an ObjectId, before any ObjectId-based references can be translated, so that has to be done by DeepCloneObjects() as part of the translation process, which itself cannot happen until after all objects involved in the operation have been deep-cloned, and added to a database/owner, and are assigned ObjectIds.

 

If I'm not mistaken, DBObject.DeepClone() does not clone its own extension dictionary. That's a separate DBObject that must also have its DeepClone() implementation called, and that might explain quite a bit. There's also fixing up of the DrawOrderTable, and probably tons of other things I've overlooked, but I think I've already mentioned that directly-calling DeepClone() on a DBObject may work some times, but probably will not work every time.

 

 

0 Likes
Message 4 of 5

ActivistInvestor
Mentor
Mentor

I should have mentioned this in my previous reply, but forgot to.

 

There's a very simple reason why you can't call DeepClone(). It requires an IdMapping that must be set up properly for the deep clone operation, which means setting the DuplicateRecordCloning, DeepCloneContext, OriginalDatabase, and DestinationDatabase properties of the IdMapping. I highlighted the parts of the quoted code from your post below. Notice that you're not setting any of the aforementioned properties, and in fact you can't because they're all read-only.

 

So, without a properly-initialized IdMapping, DeepClone() isn't going to work as expected.

 

 

 

 


@norman.yuan wrote:

Up until now, I have been using Database.WblockCloneObjects() to copy entities from other drawings, and using Database.DeepCloneObjects() to copy entities in the same drawing/database many times and never feld the need to call Entity.DeepClone() on individual entities.

 

In my current project, I run into the need to copy a single entity. Since when user decides to create a copy of the entity, it has already been opend in a trnasaction (as Entity), I though I'd simply call Entity.DeepClone() to create the copy, instead calling Database.DeepCloneObjects() as I am used to. I was surprised to find out the entity copy generated by Entity.DeepClone() does not show in AutoCAD editor, until the drawing is closed and re-opened in AutoCAD. Of course, when cloneing entity with Database.DeepCloneObjects(), this issue does not occur.

 

Here is the code (and video clip) to show the difference of the 2 cloning approaches:

 

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

[assembly: CommandClass(typeof(ODDataDeepClone.MyCommands))]

namespace ODDataDeepClone
{
    public class MyCommands
    {
        [CommandMethod("DoClone")]
        public static void RunMyCommand()
        {
            var doc = CadApp.DocumentManager.MdiActiveDocument;
            var ed = doc.Editor;

            ObjectId entId;
            Matrix3d transform;
            bool entClone;

            if (GetInputs(ed, out entId, out transform, out entClone))
            {
                if (entClone)
                {
                    DoEntityDeepClone(entId, transform);

                    // Following 2 lines of code have no effect
                    // to get DeepCloned entity showing
                    ////doc.TransactionManager.FlushGraphics();
                    ////ed.Regen();
                }
                else
                {
                    DoDatabaseDeepClone(entId, transform);
                }
            }
        }

        private static bool GetInputs(
            Editor ed,
            out ObjectId entId,
            out Matrix3d transform,
            out bool entDeepClone)
        {
            entId = ObjectId.Null;
            transform = new Matrix3d();
            entDeepClone = true;

            var eRes = ed.GetEntity("\nSelect an entity:");
            if (eRes.Status == PromptStatus.OK)
            {
                entId = eRes.ObjectId;
                var basePt = eRes.PickedPoint;

                var opt = new PromptPointOptions(
                    "\nSelect \"Move-To\" point:");
                opt.UseBasePoint = true;
                opt.BasePoint = basePt;

                var pRes = ed.GetPoint(opt);
                if (pRes.Status == PromptStatus.OK)
                {
                    transform = Matrix3d.Displacement(
                        basePt.GetVectorTo(pRes.Value));

                    var kOpt = new PromptKeywordOptions(
                        "\nEntity.DeepClone() or Database.DeepCloneObject()?");
                    kOpt.Keywords.Add("Entity");
                    kOpt.Keywords.Add("Database");
                    kOpt.Keywords.Default = "Entity";

                    var kRes = ed.GetKeywords(kOpt);
                    if (kRes.Status == PromptStatus.OK)
                    {
                        entDeepClone =
                            kRes.StringResult == "Entity" ? true : false;
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }
                else
                {
                    return false;
                }
            }
            else
            {
                return false;
            }
        }

        private static void DoEntityDeepClone(
            ObjectId entId, Matrix3d transform)
        {
            using (var tran =
                entId.Database.TransactionManager.StartTransaction())
            {
                var ent = (Entity)tran.GetObject(entId, OpenMode.ForRead);

                var owner = (BlockTableRecord)tran.GetObject(
                    ent.OwnerId, OpenMode.ForWrite);
                var mapping = new IdMapping();
                var cloned = (Entity)ent.DeepClone(owner, mapping, true);                cloned.TransformBy(transform);
                tran.AddNewlyCreatedDBObject(cloned, true);
                tran.Commit();
            }
        }

        private static void DoDatabaseDeepClone(
            ObjectId entId, Matrix3d transform)
        {
            using (var tran =
                entId.Database.TransactionManager.StartTransaction())
            {
                var ent = (Entity)tran.GetObject(entId, OpenMode.ForRead);

                ObjectId ownerId = ent.OwnerId;
                var mapping = new IdMapping();
                ObjectIdCollection ids = new ObjectIdCollection(
                    new ObjectId[] { entId });
                entId.Database.DeepCloneObjects(ids, ownerId, mapping, false);
                foreach (IdPair pair in mapping)
                {
                    if (pair.IsCloned)
                    {
                        var cloned = tran.GetObject(
                            pair.Value, OpenMode.ForRead) as Entity;
                        if (cloned != null)
                        {
                            cloned.UpgradeOpen();
                            cloned.TransformBy(transform);
                        }
                    }
                }

                tran.Commit();
            }
        }
    }
}

I delibrately move the cloned entity away from its source, so the cloning can be visibly witnessed (well, in the case of calling Database.DeepCloneObjects().

 

I did a search and did not find any thing on this issue. Am I missing something? I tried the same code with AutoCAD 2017 and 2018, got the same result. Anyone knows on this?

 

As I said, I have been always used Database.DeepCloneObjects(), so, this issue is not clritical to me at all, but like to know if I missed something?

 

 


 

0 Likes
Message 5 of 5

neyton_
Advocate
Advocate

same problem...

 

open entity for write and put Visible property to True:

 

Dim o As Entity = trans.GetAcadObject(p.Value, ForWrite)
o.Visible = True
o.RecordGraphicsModified(True

works for me

0 Likes