Existing Layout Names and Creating and Setting a New Layout

Existing Layout Names and Creating and Setting a New Layout

Civil3DReminders_com
Mentor Mentor
1,665 Views
5 Replies
Message 1 of 6

Existing Layout Names and Creating and Setting a New Layout

Civil3DReminders_com
Mentor
Mentor

I'm creating some layouts and viewports and the code works great, except I don't have a way to check if the existing layout name exists. So I created an extension method to get the list of existing layouts (below).

 

public static List<string> GetLayoutNames(this Database db)
        {
            var existSheetNames = new List<string>();
            var layoutDict = db.LayoutDictionaryId.GetObject(OpenMode.ForRead) as DBDictionary;
            foreach (var layout in layoutDict)
            {
                existSheetNames.Add(layout.Key);
            }
            return existSheetNames;
        }

 

Once I do that, then setting a layout returns an error "eSetFailed" with this code at the lm.CurrentLayout = name; line:

        public static ObjectId CreateAndMakeLayoutCurrent(LayoutManager lm, string name, bool select =true)
        {
            //try and get Layout
            ObjectId id = lm.GetLayoutId(name);
            try
            {
                if (!id.IsValid)
                {
                    id = lm.CreateLayout(name);
                }
                lm.CurrentLayout = name;                
            }
            catch(System.Exception ex)
            {
                Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\n" + ex.Message);
            }
            return id;
        }

I'm using a basic command method: [CommandMethodAttribute("CtCreateLayoutsBySheets")]

 

I'm out of ideas. Any ideas?

 

Civil Reminders
http://blog.civil3dreminders.com/
http://www.CivilReminders.com/
Alumni
0 Likes
1,666 Views
5 Replies
Replies (5)
Message 2 of 6

Civil3DReminders_com
Mentor
Mentor

The code needs to be in two separate transactions, then it works as expected. I tried it, but I had a transaction with the two transactions later, and then it also fails.

Civil Reminders
http://blog.civil3dreminders.com/
http://www.CivilReminders.com/
Alumni
0 Likes
Message 3 of 6

brianchapmandesign
Collaborator
Collaborator

Two transactions, as in need to commit the first before the 2nd will work?  Or just run two transactions and commit both at end?

 

Just curious.


@Civil3DReminders_com wrote:

The code needs to be in two separate transactions, then it works as expected. I tried it, but I had a transaction with the two transactions later, and then it also fails.


 


"Very funny, Scotty. Now beam down my clothes.
0 Likes
Message 4 of 6

If the call to existLayoutNames = db.GetLayoutNames(); is in the transaction that creates the layouts then it will fail. As done below with the two transaction doesn't error out.

 

        public static bool CreateMultipleLayouts(List<ObjectId> sheetObjIdsToPlot)
        {
            bool created = false;
            Document Adoc = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
            Editor ed = Adoc.Editor;
            Database db = Adoc.Database;

            var layerStates = new List<LayerState>();

            var objIdsHidden = new List<ObjectId>();

            try
            {
                CollectLayersAndTurnOff(layerStates);
                var showPageSetupForNewLayouts = System.Convert.ToInt32(Application.GetSystemVariable("SHOWPAGESETUPFORNEWLAYOUTS"));

                if (showPageSetupForNewLayouts != 0)
                {
                    Application.SetSystemVariable("SHOWPAGESETUPFORNEWLAYOUTS", 0);
                }

                var existLayoutNames = new List<string>();

                using (var tr = db.TransactionManager.StartTransaction())
                {
                    existLayoutNames = db.GetLayoutNames();
                }
                
                using (Transaction ts = db.TransactionManager.StartTransaction())
                {
                    
                    objIdsHidden = db.HideAllObjects();

                    int counter = sheetObjIdsToPlot.Count;
                    
                    try
                    {
                        var modelSpaceScale = CtUtilities.GetModelSpaceScale();
                        var sheetInfos = new List<SheetInformation>();

                        // Collect the sheet info first, and then create them. 
                        // This way the scale stays the same and hopefully the layouts come out correct.
                        foreach (var sheetObjId in sheetObjIdsToPlot)
                        {
                            var sheet = sheetObjId.GetObject(OpenMode.ForRead) as Sheet;
                            Extents3d sheetExtents3d = sheet.GeometricExtents;
                            sheetExtents3d.TransformBy(ed.CurrentUserCoordinateSystem.Inverse());
                            
                            var sheetInfo = new SheetInformation()
                            {
                                LayoutName = ("CustomLayout_" + counter).CheckNameAndIncrement(existLayoutNames, false),
                                ModelSpaceMinPoint = sheetExtents3d.MinPoint,
                                ModelSpaceMaxPoint = sheetExtents3d.MaxPoint,
                                IsLandscape = sheet.IsLandscape()
                            };
                            sheetInfos.Add(sheetInfo);
                            counter--;
                        }

                        var currentSpace = LayoutManager.Current.CurrentLayout;

                        foreach (var sheetInfo in sheetInfos)
                        {
                            CreateViewPort(sheetInfo, modelSpaceScale);
                        }

                        LayoutManager.Current.CurrentLayout = currentSpace;
                    }
                    catch (System.Exception ex)
                    {
                        ed.WriteMessage("\nCreateMultipleLayouts1: " + ex.Message);
                    }
                    finally
                    {
                        if ((int)showPageSetupForNewLayouts != 0)
                        {
                            Application.SetSystemVariable("SHOWPAGESETUPFORNEWLAYOUTS", showPageSetupForNewLayouts);
                        }

                        db.ShowAllObjects(objIdsHidden);
                    }
                    ts.Commit();
                }
            }
            catch (System.Exception ex)
            {
                ed.WriteMessage("\nCreateMultipleLayouts2: " + ex.Message);
            }
            finally
            {
                TurnOnLayers(layerStates);
            }
            
            return created;
        }
Civil Reminders
http://blog.civil3dreminders.com/
http://www.CivilReminders.com/
Alumni
0 Likes
Message 5 of 6

norman.yuan
Mentor
Mentor

Well, in your original post you did not show exactly the command method that put the 2 code snippets together, wrapped in one transaction, that fails, as you claimed. The code in the your latest post you also did not show the situation of how the code fails. But I guessed I knew what you meant, so I went ahead for a test run with exactly with your 2 code snippets and put them in ONE TRANSACTION in a commandmethod, as following:

 

public class OpenLayoutTest
    {
        [CommandMethod("CreateMyLayout")]

        public static void CreateLayouts()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;

            using (var tran = dwg.TransactionManager.StartTransaction())
            {
                var lNames = dwg.Database.GetLayoutNames();

                var newLayoutId = CreateAndMakeLayoutCurrent(
                    LayoutManager.Current, "New Layout");

                tran.Commit();
            }
        }

        public static ObjectId CreateAndMakeLayoutCurrent(
            LayoutManager lm, string name, bool select = true)
        {
            //try and get Layout
            ObjectId id = lm.GetLayoutId(name);
            try
            {
                if (!id.IsValid)
                {
                    id = lm.CreateLayout(name);
                }
                lm.CurrentLayout = name;
            }
            catch (System.Exception ex)
            {
                Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager
                    .MdiActiveDocument.Editor.WriteMessage("\n" + ex.Message);
            }
            return id;
        }

    }

    public static class DBExtensions
    {
        public static List<string> GetLayoutNames(this Database db)
        {
            var existSheetNames = new List<string>();
            var layoutDict = db.LayoutDictionaryId.GetObject(OpenMode.ForRead) as DBDictionary;
            foreach (var layout in layoutDict)
            {
                existSheetNames.Add(layout.Key);
            }
            return existSheetNames;
        }
    }

As you can see, in the CommandMethod class, I simply wrapped your 2 pieces of code in a transaction. The code runs without error: the first run creates a new layout ("New Layout"); the following run, make the "New Layout" current, if it wasn't. No error. I tested with Acad2018, but would not think it would be otherwise with 2017, 2019, or whatever Acad version.

 

So, since you did not show full code that led to error, I suspect there could be other reasons that causes error. For example, LayoutManager.Current.CurrentLayout = "xxxx" would raise exception if called in ApplicationContext without current document being locked.

 

There are also a few things:

1. In your latest code, you have 

                using (var tr = db.TransactionManager.StartTransaction())
                {
                    existLayoutNames = db.GetLayoutNames();
                }

It is not a good practice to start a transaction (even for reading only) and not commit it.

2. If you create an extension method to get data from whatever object (in your case, it is Database), it would be better to make it self-contained, and no dependency to outside environment. And if there is dependency, then you add argument to the method so that the dependency could be injected when the method is calling. Here I am talking about the need to using a Transaction to wrap your extension method call, which is not good design, IMO. you should:

        public static List<string> GetLayoutNames(this Database db)
        {
            var existSheetNames = new List<string>();
using (var tran = db.TransactionManager.StartTransaction())
{ var layoutDict = db.LayoutDictionaryId.GetObject(OpenMode.ForRead) as DBDictionary; foreach (var layout in layoutDict) { existSheetNames.Add(layout.Key); }
tran.Commit();
} return existSheetNames; }

3. When using LayoutManager, there is no need to wrap its method calls with Transaction, it methods internally starts transaction and commit it as needed

 

With the suggestion I made in 2 and 3, the command method I showed on top could be simplified to:

[CommandMethod("CreateMyLayout")]

        public static void CreateLayouts()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;
            
            var lNames = dwg.Database.GetLayoutNames();
            var newLayoutId = CreateAndMakeLayoutCurrent(
                LayoutManager.Current, "New Layout");
        }

That is, no need to wrap the Database extension method GetLayoutNames() and CreateAndMakeLayoutCurrent(). This, of course works with my test (Acad2018).

 

Again, I have no idea where the error you saw came from.

 

 

Norman Yuan

Drive CAD With Code

EESignature

0 Likes
Message 6 of 6

Civil3DReminders_com
Mentor
Mentor

Thanks for looking at it Norman.

Civil Reminders
http://blog.civil3dreminders.com/
http://www.CivilReminders.com/
Alumni
0 Likes