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