Author Topic: Missing viewport geometry (using deepclone)  (Read 339 times)

0 Members and 2 Guests are viewing this topic.

djee

  • Newt
  • Posts: 48
Missing viewport geometry (using deepclone)
« on: January 09, 2017, 05:48:40 pm »
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: [Select]
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


gile

  • Water Moccasin
  • Posts: 2089
  • Marseille, France
Re: Missing viewport geometry (using deepclone)
« Reply #1 on: January 10, 2017, 08:03:26 am »
Hi,

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

Code - C#: [Select]
  1. using Autodesk.AutoCAD.DatabaseServices;
  2. using Autodesk.AutoCAD.EditorInput;
  3. using Autodesk.AutoCAD.Runtime;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using AcAp = Autodesk.AutoCAD.ApplicationServices.Core.Application;
  8.  
  9. [assembly: CommandClass(typeof(CopyLayout.Commands))]
  10.  
  11. namespace CopyLayout
  12. {
  13.    public class Commands
  14.    {
  15.        [CommandMethod("TEST")]
  16.        public void Test()
  17.        {
  18.            var doc = AcAp.DocumentManager.MdiActiveDocument;
  19.            var db = doc.Database;
  20.            var ed = doc.Editor;
  21.            using (var tr = db.TransactionManager.StartTransaction())
  22.            {
  23.                // get the layout names
  24.                var layoutDict = (DBDictionary)tr.GetObject(db.LayoutDictionaryId, OpenMode.ForRead);
  25.                var layoutNames = new HashSet<string>();
  26.                foreach (DBDictionaryEntry entry in layoutDict)
  27.                {
  28.                    layoutNames.Add(entry.Key);
  29.                }
  30.  
  31.                // get the name of the layout to be copied
  32.                var strOpts = new PromptStringOptions("\nEnter the name of the layout to be copied: ");
  33.                strOpts.AllowSpaces = true;
  34.  
  35.                var comparer = new NameComparer();
  36.                string copyName;
  37.                while (true)
  38.                {
  39.                    var strRes = ed.GetString(strOpts);
  40.                    if (strRes.Status != PromptStatus.OK)
  41.                        return;
  42.                    if (layoutNames.Contains(strRes.StringResult, comparer))
  43.                    {
  44.                        copyName = strRes.StringResult;
  45.                        break;
  46.                    }
  47.                    ed.WriteMessage($"\nLayout '{strRes.StringResult}' not found.");
  48.                }
  49.  
  50.                // get the name of the new layout
  51.                strOpts.Message = "\nEnter the new layout name: ";
  52.                string newName;
  53.                while (true)
  54.                {
  55.                    var strRes = ed.GetString(strOpts);
  56.                    if (strRes.Status != PromptStatus.OK)
  57.                        return;
  58.                    if (!layoutNames.Contains(strRes.StringResult, comparer))
  59.                    {
  60.                        newName = strRes.StringResult;
  61.                        break;
  62.                    }
  63.                    ed.WriteMessage($"\nLayout '{strRes.StringResult}' already exists.");
  64.                }
  65.  
  66.                // copy the layout and set current the new layout tab
  67.                var layoutMgr = LayoutManager.Current;
  68.                layoutMgr.CopyLayout(copyName, newName);
  69.                layoutMgr.CurrentLayout = newName;
  70.  
  71.                tr.Commit();
  72.            }
  73.        }
  74.  
  75.        class NameComparer : IEqualityComparer<string>
  76.        {
  77.            public bool Equals(string x, string y) =>
  78.                x.Equals(y, StringComparison.CurrentCultureIgnoreCase);
  79.  
  80.            public int GetHashCode(string obj) =>
  81.                obj.ToUpper().GetHashCode();
  82.        }
  83.    }
  84. }
  85.  
Speaking English as a French Frog

djee

  • Newt
  • Posts: 48
Re: Missing viewport geometry (using deepclone)
« Reply #2 on: January 10, 2017, 09:10:05 am »
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

  • Water Moccasin
  • Posts: 2089
  • Marseille, France
Re: Missing viewport geometry (using deepclone)
« Reply #3 on: January 10, 2017, 10:57:17 am »
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#: [Select]
  1. using Autodesk.AutoCAD.DatabaseServices;
  2. using Autodesk.AutoCAD.EditorInput;
  3. using Autodesk.AutoCAD.Runtime;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using AcAp = Autodesk.AutoCAD.ApplicationServices.Core.Application;
  8.  
  9. [assembly: CommandClass(typeof(CopyLayout.Commands))]
  10.  
  11. namespace CopyLayout
  12. {
  13.    public class Commands
  14.    {
  15.        [CommandMethod("TEST", CommandFlags.NoTileMode)]
  16.        public void Test()
  17.        {
  18.            var doc = AcAp.DocumentManager.MdiActiveDocument;
  19.            var db = doc.Database;
  20.            var ed = doc.Editor;
  21.  
  22.            var selRes = ed.GetSelection();
  23.            if (selRes.Status != PromptStatus.OK)
  24.                return;
  25.            var sourceIds = new ObjectIdCollection(selRes.Value.GetObjectIds());
  26.  
  27.            using (var tr = db.TransactionManager.StartTransaction())
  28.            {
  29.                // get the layout names
  30.                var layoutNames = new HashSet<string>();
  31.                var layoutDict = (DBDictionary)tr.GetObject(db.LayoutDictionaryId, OpenMode.ForRead);
  32.                foreach (var entry in layoutDict) layoutNames.Add(entry.Key);
  33.  
  34.                // get the layout name to copy selected objects to
  35.                string layoutName;
  36.                var comparer = new NameComparer();
  37.                var strOpts = new PromptStringOptions("\nLayout name to copy selected objects to: ");
  38.                strOpts.AllowSpaces = true;
  39.                while (true)
  40.                {
  41.                    var strRes = ed.GetString(strOpts);
  42.                    if (strRes.Status != PromptStatus.OK)
  43.                        return;
  44.                    if (layoutNames.Contains(strRes.StringResult, comparer))
  45.                    {
  46.                        layoutName = strRes.StringResult;
  47.                        break;
  48.                    }
  49.                    ed.WriteMessage($"\nLayout '{strRes.StringResult}' not found.");
  50.                    tr.Commit();
  51.                }
  52.  
  53.                var layout = (Layout)tr.GetObject(layoutDict.GetAt(layoutName), OpenMode.ForRead);
  54.  
  55.                var mapping = new IdMapping();
  56.                db.DeepCloneObjects(sourceIds, layout.BlockTableRecordId, mapping, false);
  57.  
  58.                // set the specified layout current and update the viewports
  59.                LayoutManager.Current.CurrentLayout = layoutName;
  60.                foreach (IdPair pair in mapping)
  61.                {
  62.                    if (pair.Key.ObjectClass.Name == "AcDbViewport" && pair.IsCloned)
  63.                    {
  64.                        var vp = (Viewport)tr.GetObject(pair.Value, OpenMode.ForWrite);
  65.                        vp.On = false;
  66.                        vp.On = true;
  67.                    }
  68.                }
  69.                tr.Commit();
  70.            }
  71.        }
  72.  
  73.        class NameComparer : IEqualityComparer<string>
  74.        {
  75.            public bool Equals(string x, string y) =>
  76.                x.Equals(y, StringComparison.CurrentCultureIgnoreCase);
  77.  
  78.            public int GetHashCode(string obj) =>
  79.                obj.ToUpper().GetHashCode();
  80.        }
  81.    }
  82. }
  83.  
Speaking English as a French Frog

djee

  • Newt
  • Posts: 48
Re: Missing viewport geometry (using deepclone)
« Reply #4 on: January 10, 2017, 11:39:45 am »
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: [Select]
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

Moving the DeepCloning outside the For Each Loop seemed to have fix my issue...
Code: [Select]
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)

Once again, thanks a lot Gile!!
« Last Edit: January 10, 2017, 11:58:39 am by djee »