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

0 Members and 1 Guest are viewing this topic.

djee

  • Newt
  • Posts: 49
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

  • Gator
  • Posts: 2507
  • 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: 49
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

  • Gator
  • Posts: 2507
  • 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: 49
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 »