Code Red > .NET

Missing viewport geometry (using deepclone)

(1/1)

djee:
I'm trying to copy selected objects (using SelectionSet) from one layout to another. For some reason some of the copied Viewports seems to be "unresponsive & corrupted". I can no longer activate them or modify them... I've attach an image showing the original layout & the copied layout (you can see some viewports are blank). Is there a reason for this weird behavior?
Here's my code:

--- Code: ---Imports Autodesk.AutoCAD.ApplicationServices
Imports Autodesk.AutoCAD.DatabaseServices
Imports Autodesk.AutoCAD.EditorInput

Public Class Copy
    Public Shared Sub CopyArea()
        ' Get the current document and database
        Dim acDoc As Document = Application.DocumentManager.MdiActiveDocument
        Dim acCurDb As Database = acDoc.Database
        Dim acDocEd As Editor = Application.DocumentManager.MdiActiveDocument.Editor
        Dim layoutCollectionName As List(Of String) = New List(Of String)
        Dim acObjIdColl As ObjectIdCollection = New ObjectIdCollection()
        Dim idcol As ObjectIdCollection = New ObjectIdCollection()
        '' Start a transaction
        Using acTrans As Transaction = acCurDb.TransactionManager.StartTransaction()
            '' Request for objects to be selected in the drawing area
            Dim acSSPrompt As PromptSelectionResult = acDoc.Editor.GetSelection()
            '' If the prompt status is OK, objects were selected
            If acSSPrompt.Status = PromptStatus.OK Then
                Dim acSSet As SelectionSet = acSSPrompt.Value
                'Append selected objects to the ObjectIdCollection
                acObjIdColl = New ObjectIdCollection(acSSet.GetObjectIds())

                'get layout dictionnary
                Dim lays As DBDictionary = acTrans.GetObject(acCurDb.LayoutDictionaryId, OpenMode.ForRead)
                For Each layoutEntry As DictionaryEntry In lays
                    Dim existingLayoutId As ObjectId = layoutEntry.Value
                    Dim existingLayout As Layout = TryCast(acTrans.GetObject(existingLayoutId, OpenMode.ForRead), Layout)
                    layoutCollectionName.Add(existingLayout.LayoutName)
                Next

                'prompt for which layout we should copy the selected item
                Dim pKeyOpts As PromptStringOptions = New PromptStringOptions("Layout name to copy selected objects to :")
                Dim pKeyRes As PromptResult = acDoc.Editor.GetString(pKeyOpts)
                If pKeyRes.Status = PromptStatus.OK Then
                    'check if the entered string matches an existing layout name
                    If Not layoutCollectionName.Contains(pKeyRes.StringResult) Then
                        Application.ShowAlertDialog("Entered layout name: " & pKeyRes.StringResult & " does not exist. Exiting application.")
                        Exit Sub
                    End If

                    Using acLckDoc As DocumentLock = acDoc.LockDocument()

                        'get the selected layout BlockTableRercord
                        Dim selectLayoutName As Layout = TryCast(lays.GetAt(pKeyRes.StringResult).GetObject(OpenMode.ForRead), Layout)
                        Dim selectedLayoutBtr As BlockTableRecord = TryCast(acTrans.GetObject(selectLayoutName.BlockTableRecordId, OpenMode.ForRead), BlockTableRecord)

                        'Copy the object to the selected layout
                        For Each id As ObjectId In acObjIdColl
                            'check if entity is a viewport
                            If (id.ObjectClass.DxfName.ToUpper = "VIEWPORT") Then
                                idcol.Add(id)
                                Dim idmap As New IdMapping
                                selectLayoutName.UpgradeOpen()
                                acCurDb.DeepCloneObjects(idcol, selectLayoutName.BlockTableRecordId, idmap, False)
                            Else
                                Dim ent As Entity = DirectCast(acTrans.GetObject(id, OpenMode.ForWrite, False), Entity)
                                Dim copiedEnt As Entity = DirectCast(ent.Clone(), Entity)
                                selectedLayoutBtr.UpgradeOpen()
                                selectedLayoutBtr.AppendEntity(copiedEnt)
                                acTrans.AddNewlyCreatedDBObject(copiedEnt, True)
                            End If
                        Next
                    End Using

                End If
                End If
            acTrans.Commit()
            acDocEd.Regen()
        End Using
    End Sub
End Class


--- End code ---

gile:
Hi,

What about simply copuing the whole layout with LayoutManagerCopyLayout()?


--- Code - C#: ---using Autodesk.AutoCAD.DatabaseServices;using Autodesk.AutoCAD.EditorInput;using Autodesk.AutoCAD.Runtime;using System;using System.Collections.Generic;using System.Linq;using AcAp = Autodesk.AutoCAD.ApplicationServices.Core.Application; [assembly: CommandClass(typeof(CopyLayout.Commands))] namespace CopyLayout{    public class Commands    {        [CommandMethod("TEST")]        public void Test()        {            var doc = AcAp.DocumentManager.MdiActiveDocument;            var db = doc.Database;            var ed = doc.Editor;            using (var tr = db.TransactionManager.StartTransaction())            {                // get the layout names                var layoutDict = (DBDictionary)tr.GetObject(db.LayoutDictionaryId, OpenMode.ForRead);                var layoutNames = new HashSet<string>();                foreach (DBDictionaryEntry entry in layoutDict)                {                    layoutNames.Add(entry.Key);                }                 // get the name of the layout to be copied                var strOpts = new PromptStringOptions("\nEnter the name of the layout to be copied: ");                strOpts.AllowSpaces = true;                 var comparer = new NameComparer();                string copyName;                while (true)                {                    var strRes = ed.GetString(strOpts);                    if (strRes.Status != PromptStatus.OK)                        return;                    if (layoutNames.Contains(strRes.StringResult, comparer))                    {                        copyName = strRes.StringResult;                        break;                    }                    ed.WriteMessage($"\nLayout '{strRes.StringResult}' not found.");                }                 // get the name of the new layout                strOpts.Message = "\nEnter the new layout name: ";                string newName;                while (true)                {                    var strRes = ed.GetString(strOpts);                    if (strRes.Status != PromptStatus.OK)                        return;                    if (!layoutNames.Contains(strRes.StringResult, comparer))                    {                        newName = strRes.StringResult;                        break;                    }                    ed.WriteMessage($"\nLayout '{strRes.StringResult}' already exists.");                }                 // copy the layout and set current the new layout tab                var layoutMgr = LayoutManager.Current;                layoutMgr.CopyLayout(copyName, newName);                layoutMgr.CurrentLayout = newName;                 tr.Commit();            }        }         class NameComparer : IEqualityComparer<string>        {            public bool Equals(string x, string y) =>                x.Equals(y, StringComparison.CurrentCultureIgnoreCase);             public int GetHashCode(string obj) =>                 obj.ToUpper().GetHashCode();        }    }} 

djee:
Hi Gile,
My goal was to copy some specific objects to a freshly inserted drawing frame... I just saw that, using my code, I see that it copies multiple viewport, one on top of each other...

gile:
Sorry, I misunderstood.

From the test I did, this seems to work: after deep cloning, set the specified layout current and force update each newly cloned viewport by setting it Off and On.


--- Code - C#: ---using Autodesk.AutoCAD.DatabaseServices;using Autodesk.AutoCAD.EditorInput;using Autodesk.AutoCAD.Runtime;using System;using System.Collections.Generic;using System.Linq;using AcAp = Autodesk.AutoCAD.ApplicationServices.Core.Application; [assembly: CommandClass(typeof(CopyLayout.Commands))] namespace CopyLayout{    public class Commands    {        [CommandMethod("TEST", CommandFlags.NoTileMode)]        public void Test()        {            var doc = AcAp.DocumentManager.MdiActiveDocument;            var db = doc.Database;            var ed = doc.Editor;             var selRes = ed.GetSelection();            if (selRes.Status != PromptStatus.OK)                return;            var sourceIds = new ObjectIdCollection(selRes.Value.GetObjectIds());             using (var tr = db.TransactionManager.StartTransaction())            {                // get the layout names                var layoutNames = new HashSet<string>();                var layoutDict = (DBDictionary)tr.GetObject(db.LayoutDictionaryId, OpenMode.ForRead);                foreach (var entry in layoutDict) layoutNames.Add(entry.Key);                 // get the layout name to copy selected objects to                string layoutName;                var comparer = new NameComparer();                var strOpts = new PromptStringOptions("\nLayout name to copy selected objects to: ");                strOpts.AllowSpaces = true;                while (true)                {                    var strRes = ed.GetString(strOpts);                    if (strRes.Status != PromptStatus.OK)                        return;                    if (layoutNames.Contains(strRes.StringResult, comparer))                    {                        layoutName = strRes.StringResult;                        break;                    }                    ed.WriteMessage($"\nLayout '{strRes.StringResult}' not found.");                    tr.Commit();                }                 var layout = (Layout)tr.GetObject(layoutDict.GetAt(layoutName), OpenMode.ForRead);                 var mapping = new IdMapping();                db.DeepCloneObjects(sourceIds, layout.BlockTableRecordId, mapping, false);                 // set the specified layout current and update the viewports                LayoutManager.Current.CurrentLayout = layoutName;                foreach (IdPair pair in mapping)                {                    if (pair.Key.ObjectClass.Name == "AcDbViewport" && pair.IsCloned)                    {                        var vp = (Viewport)tr.GetObject(pair.Value, OpenMode.ForWrite);                        vp.On = false;                        vp.On = true;                    }                }                tr.Commit();            }        }         class NameComparer : IEqualityComparer<string>        {            public bool Equals(string x, string y) =>                x.Equals(y, StringComparison.CurrentCultureIgnoreCase);             public int GetHashCode(string obj) =>                obj.ToUpper().GetHashCode();        }    }} 

djee:
I've just realised (thanks to you) that I was deepcloning each entity in my For Each Loop. That would explain why I had a bunch of viewport one on top of each other... DeepCloning the same collection again & again...

--- Code: ---For Each id As ObjectId In acObjIdColl
        '                        'check if entity is a viewport
        '                        If (id.ObjectClass.DxfName.ToUpper = "VIEWPORT") Then
        '                            idcol.Add(id)
        '                            Dim idmap As New IdMapping
        '                            selectLayoutName.UpgradeOpen()
        '                            acCurDb.DeepCloneObjects(idcol, selectLayoutName.BlockTableRecordId, idmap, False)
        '                        Else
        '                            Dim ent As Entity = DirectCast(acTrans.GetObject(id, OpenMode.ForWrite, False), Entity)
        '                            Dim copiedEnt As Entity = DirectCast(ent.Clone(), Entity)
        '                            selectedLayoutBtr.UpgradeOpen()
        '                            selectedLayoutBtr.AppendEntity(copiedEnt)
        '                            acTrans.AddNewlyCreatedDBObject(copiedEnt, True)
        '                        End If
--- End code ---

Moving the DeepCloning outside the For Each Loop seemed to have fix my issue...

--- Code: --- For Each id As ObjectId In acObjIdColl
                                Dim obj As DBObject = acTrans.GetObject(id, OpenMode.ForRead)
                                Dim dbViewport As Viewport = TryCast(obj, Viewport)
                                If dbViewport IsNot Nothing Then
                                    idcol.Add(id)
                                Else
                                    Dim ent As Entity = DirectCast(acTrans.GetObject(id, OpenMode.ForWrite, False), Entity)
                                    Dim copiedEnt As Entity = DirectCast(ent.Clone(), Entity)
                                    selectedLayoutBtr.UpgradeOpen()
                                    selectedLayoutBtr.AppendEntity(copiedEnt)
                                    acTrans.AddNewlyCreatedDBObject(copiedEnt, True)
                                End If
                            Next

                            'go for the deepcloning on the ViewportCollection
                            Dim idmap As New IdMapping
                            selectLayoutName.UpgradeOpen()
                            acCurDb.DeepCloneObjects(idcol, selectLayoutName.BlockTableRecordId, idmap, False)
--- End code ---

Once again, thanks a lot Gile!!

Navigation

[0] Message Index

Go to full version