Author Topic: Opening entites on locked layer  (Read 5912 times)

0 Members and 1 Guest are viewing this topic.

Jeff H

  • Needs a day job
  • Posts: 6150
Opening entites on locked layer
« on: December 06, 2011, 03:15:29 AM »
Lets say you are changing all entities in block definitions to be on layer 0 and by layer
and you do something like
 
Code: [Select]
        [CommandMethod("BlkDefsToByLayerAndLayerZero")]
        public void BlkDefsToByLayerAndLayerZero()
        {
            using (Transaction trx = Db.TransactionManager.StartTransaction())
            {
                BlockTable bt = (BlockTable)trx.GetObject(Db.BlockTableId, OpenMode.ForRead);
                foreach (ObjectId btrId in bt)
                {
                    BlockTableRecord btr = (BlockTableRecord)trx.GetObject(btrId, OpenMode.ForRead);
                    if (!btr.IsLayout)
                    {
                        foreach (ObjectId entId in btr)
                        {
                            Entity ent = (Entity)trx.GetObject(entId, OpenMode.ForRead, false, true);
                            if (ent.LayerId != Db.LayerZero)
                            {
                                ent.UpgradeOpen();
                                ent.LayerId = Db.LayerZero;
                            }
                            if (ent.ColorIndex != 256)
                            {
                                ent.UpgradeOpen();
                                ent.ColorIndex = 256;
                            }
                        }
                    }
                }
                trx.Commit();
            }
        }
This will still fail on entities with locked layers

What is normal procedure or better
1. To open all entities ForWrite
2. Something like
Code: [Select]
                foreach (ObjectId entId in modelBtr)
                {
                    Entity ent = (Entity)trx.GetObject(entId, OpenMode.ForRead, false, true);
                    if (ent.ColorIndex != 256)
                    {
                        ent = (Entity)trx.GetObject(entId, OpenMode.ForWrite, false, true);
                        ent.ColorIndex = 256;
                    }
                }

I would not imagine unlocking the layer storing the id and locking it back would be more efficient than opening all objects, or it might be depending on the drawing.
 
So what would be the best method to use when you have no idea if the drawing will have one layer that is locked with 10,000 block definitions, or 10,000 locked layers with 10 block definitions, etc.....?
 
 
« Last Edit: April 09, 2012, 10:02:10 PM by Jeff H »

Bryco

  • Water Moccasin
  • Posts: 1883
Re: Opening entites on frozen layer
« Reply #1 on: December 06, 2011, 10:01:17 AM »
I would hope the odds that someone who makes 10000 blocks would use layer zero as rookies don't use as many blocks. Also,  if a locked layer has been used in the making of one block there is probably a good chance it has been used in the making of another block.  So opening the locked layer as it comes up and saving it in a list is probably working with the odds

BlackBox

  • King Gator
  • Posts: 3770
Re: Opening entites on frozen layer
« Reply #2 on: December 06, 2011, 10:37:08 AM »
During iteration of entities, I (being a LISPer) typically include a check for locked layer(s), then add those Layer Objects to a list (in .NET, perhaps a Dictionary is preferred?), then when all iteration is done, For Each Layer Object in our stored list (Dictionary?), we put the Lock Property back to True.

**Edit - This way you're not iterating through unnecessary layers.
"How we think determines what we do, and what we do determines what we get."

kaefer

  • Guest
Re: Opening entites on frozen layer
« Reply #3 on: December 06, 2011, 07:33:19 PM »
What is normal procedure or better
1. To open all entities ForWrite
2. Something like
Code: [Select]
                foreach (ObjectId entId in modelBtr)
                {
                    Entity ent = (Entity)trx.GetObject(entId, OpenMode.ForRead, false, true);
                    if (ent.ColorIndex != 256)
                    {
                        ent = (Entity)trx.GetObject(entId, OpenMode.ForWrite, false, true);
                        ent.ColorIndex = 256;
                    }
                }

I would imagine that you are safe to do this, as long as there's only one reader. Just like UpgradeOpen would throw if there isn't, except that the latter also throws when eOnLockedLayer.

Quote
I would not imagine unlocking the layer storing the id and locking it back would be more efficient than opening all objects, or it might be depending on the drawing.
 

What value has efficiency, taken in isolation, when you can have expressivity or even ease of maintenance too? Viz.:

Code: [Select]
       [CommandMethod("BlkDefsToByLayerAndLayerZero")]
        public void BlkDefsToByLayerAndLayerZero()
        {
            Database db = HostApplicationServices.WorkingDatabase;
            using (Transaction tr = db.TransactionManager.StartTransaction())
            {
                LayerTableRecord[] lockedLTRs =
                    db.LayerTableId
                    .GetObject<LayerTable>()
                    .GetObjects<LayerTableRecord>()
                    .Where(ltr => ltr.IsLocked)
                    .ToArray();
                foreach(LayerTableRecord lockedLTR in lockedLTRs)
                {
                    lockedLTR.UpgradeOpen();
                    lockedLTR.IsLocked = false;
                }
                IEnumerable<Entity> ents =
                    db.BlockTableId
                    .GetObject<BlockTable>()
                    .GetObjects<BlockTableRecord>()
                    .Where(btr => !btr.IsLayout && !btr.IsFromExternalReference && !btr.IsFromOverlayReference)
                    .SelectMany(btr => btr.GetObjects<Entity>())
                    .Where(ent => ent.ColorIndex != 256 || ent.LayerId != db.LayerZero);
                foreach (Entity ent in ents)
                {
                    ent.UpgradeOpen();
                    ent.LayerId = db.LayerZero;
                    ent.ColorIndex = 256;
                }
                foreach (LayerTableRecord lockedLTR in lockedLTRs)
                {
                    lockedLTR.IsLocked = true;
                }
                tr.Commit();
            }

Of course, this needs boilerplate: a method for being able to chain calls to GetObject; and one to chain the iteration of GetObject over an untyped sequence, cast and type filter included.

Code: [Select]
    public static class Extensions
    {
        // Opens a DBObject in ForRead mode
        public static T GetObject<T>(this ObjectId id)
            where T : DBObject
        {
            return id.GetObject(OpenMode.ForRead) as T;
        }
        // Opens an untyped collection of ObjectIds as DBObjects in ForRead mode
        public static IEnumerable<T> GetObjects<T>(this System.Collections.IEnumerable ids)
            where T : DBObject
        {
            return
                from ObjectId id in ids
                select id.GetObject<T>() into obj
                where obj != null
                select obj;
        }
    }
« Last Edit: December 06, 2011, 07:37:32 PM by kaefer »

Jeff H

  • Needs a day job
  • Posts: 6150
Re: Opening entites on locked layer
« Reply #4 on: June 03, 2012, 04:14:16 AM »
Thanks kaefer,
 
Bringing this back to life to see if you guys had any new idea on this.
 
Maybe using the ideas from here.
........
The reason I use the UpgradeOpen() extension method I included, is because dealing with objects on locked layers is a reality that one most contend with (especially when processing drawings that are not open in the drawing editor, or when processing blocks other than model or paper space where filtered selection sets are typically used to preclude objects on locked layers).

Code - C#: [Select]
  1. .....
  2. .....
  3. .....
  4.         public static IEnumerable<T> UpgradeOpen<T>(this IEnumerable<T> source)
  5. where T : DBObject
  6.         {
  7.             foreach (T item in source)
  8.             {
  9.                 try
  10.                 {
  11.                     if (!item.IsWriteEnabled)
  12.                         item.UpgradeOpen();
  13.                 }
  14.                 catch (Autodesk.AutoCAD.Runtime.Exception ex)
  15.                 {
  16.                     if (ex.ErrorStatus != ErrorStatus.OnLockedLayer)
  17.                         throw;
  18.                     continue;
  19.                 }
  20.                 yield return item;
  21.             }
  22.         }
  23. .....
  24. .....
  25. .....
  26.  


 
Using Tony's example above these were 2 I threw together quickly just to see if they worked and minimal testing.
 
The first one just opens it again ForWrite.
The second one unlocks the layer on the first error caught then locks all them back. Once you get it opened ForWrite locking the layer back does not seem to cause problems for later modifications.
 
Nothing great but was trying to get ideas.
Code - C#: [Select]
  1.  
  2.          public static IEnumerable<T> UpgradeOpen<T>(this IEnumerable<T> source, bool openObjectsOnLockedLayers)
  3. where T : Entity
  4.         {
  5.             bool isOnLockedlayer = true;
  6.             foreach (T item in source)
  7.             {
  8.                 try
  9.                 {
  10.                     if (!item.IsWriteEnabled)
  11.                         item.UpgradeOpen();
  12.                     isOnLockedlayer = false;
  13.                 }
  14.                 catch (Autodesk.AutoCAD.Runtime.Exception ex)
  15.                 {
  16.                     if (ex.ErrorStatus != ErrorStatus.OnLockedLayer)
  17.                         throw;
  18.                     isOnLockedlayer = true;
  19.                 }
  20.                 if (isOnLockedlayer && openObjectsOnLockedLayers)
  21.                 {                
  22.                     yield return (T)item.ObjectId.GetObject(OpenMode.ForWrite, true, true);
  23.                 }
  24.                 if (!item.IsWriteEnabled)
  25.                     continue;
  26.                 yield return item;
  27.             }
  28.         }
  29.  
  30.         public static IEnumerable<T> UpgradeOpenEvenLocked<T>(this IEnumerable<T> source, bool openObjectsOnLockedLayers)
  31. where T : Entity
  32.         {
  33.             List<LayerTableRecord> lockedLayers = new List<LayerTableRecord>();
  34.             foreach (T item in source)
  35.             {
  36.                 try
  37.                 {
  38.                     if (!item.IsWriteEnabled)
  39.                         item.UpgradeOpen();
  40.                 }
  41.                 catch (Autodesk.AutoCAD.Runtime.Exception ex)
  42.                 {
  43.                     if (ex.ErrorStatus != ErrorStatus.OnLockedLayer)
  44.                         throw;
  45.                     LayerTableRecord ltr = (LayerTableRecord)item.LayerId.GetObject(OpenMode.ForWrite);
  46.                     ltr.IsLocked = false;
  47.                     lockedLayers.Add(ltr);
  48.                     item.UpgradeOpen();
  49.                 }
  50.                 yield return item;
  51.             }
  52.             foreach (var layer in lockedLayers)
  53.             {
  54.                 layer.IsLocked = true;
  55.             }
  56.         }
  57.  

TheMaster

  • Guest
Re: Opening entites on locked layer
« Reply #5 on: June 03, 2012, 07:04:56 PM »
Regarding Kaefer's code that unlocks all locked layers; modifies objects on the locked layers; and then locks the layers again, I'm not sure I see the difference between doing that, and more simply using the GetObject() overload that forces objects on locked layers to be opened for write.

Am I missing something?

The UpgradeOpen<T>() extension method I posted wasn't really intended for use when the intent is to modify objects on locked layers.  I only use it when I want to modify only objects that are not on locked layers, and I'm not filtering out objects on locked layers beforehand as part of a selection process. Typically, I use that method in code that I want/need to use on a database that's not open in the editor, where there's no object selection.

If I'm working with user-selected objects or the result of a call to Editor.GetSelection(), I will usually set the flag that excludes objects on locked layers if I don't want to modify those, and while I may still use UpgradeOpen<T>(),  it won't have to skip objects on locked layers.

Thanks kaefer,
 
Bringing this back to life to see if you guys had any new idea on this.
 
Maybe using the ideas from here.
........
The reason I use the UpgradeOpen() extension method I included, is because dealing with objects on locked layers is a reality that one most contend with (especially when processing drawings that are not open in the drawing editor, or when processing blocks other than model or paper space where filtered selection sets are typically used to preclude objects on locked layers).

Code - C#: [Select]
  1. .....
  2. .....
  3. .....
  4.         public static IEnumerable<T> UpgradeOpen<T>(this IEnumerable<T> source)
  5. where T : DBObject
  6.         {
  7.             foreach (T item in source)
  8.             {
  9.                 try
  10.                 {
  11.                     if (!item.IsWriteEnabled)
  12.                         item.UpgradeOpen();
  13.                 }
  14.                 catch (Autodesk.AutoCAD.Runtime.Exception ex)
  15.                 {
  16.                     if (ex.ErrorStatus != ErrorStatus.OnLockedLayer)
  17.                         throw;
  18.                     continue;
  19.                 }
  20.                 yield return item;
  21.             }
  22.         }
  23. .....
  24. .....
  25. .....
  26.  


 
Using Tony's example above these were 2 I threw together quickly just to see if they worked and minimal testing.
 
The first one just opens it again ForWrite.
The second one unlocks the layer on the first error caught then locks all them back. Once you get it opened ForWrite locking the layer back does not seem to cause problems for later modifications.
 
Nothing great but was trying to get ideas.
Code - C#: [Select]
  1.  
  2.          public static IEnumerable<T> UpgradeOpen<T>(this IEnumerable<T> source, bool openObjectsOnLockedLayers)
  3. where T : Entity
  4.         {
  5.             bool isOnLockedlayer = true;
  6.             foreach (T item in source)
  7.             {
  8.                 try
  9.                 {
  10.                     if (!item.IsWriteEnabled)
  11.                         item.UpgradeOpen();
  12.                     isOnLockedlayer = false;
  13.                 }
  14.                 catch (Autodesk.AutoCAD.Runtime.Exception ex)
  15.                 {
  16.                     if (ex.ErrorStatus != ErrorStatus.OnLockedLayer)
  17.                         throw;
  18.                     isOnLockedlayer = true;
  19.                 }
  20.                 if (isOnLockedlayer && openObjectsOnLockedLayers)
  21.                 {                
  22.                     yield return (T)item.ObjectId.GetObject(OpenMode.ForWrite, true, true);
  23.                 }
  24.                 if (!item.IsWriteEnabled)
  25.                     continue;
  26.                 yield return item;
  27.             }
  28.         }
  29.  
  30.         public static IEnumerable<T> UpgradeOpenEvenLocked<T>(this IEnumerable<T> source, bool openObjectsOnLockedLayers)
  31. where T : Entity
  32.         {
  33.             List<LayerTableRecord> lockedLayers = new List<LayerTableRecord>();
  34.             foreach (T item in source)
  35.             {
  36.                 try
  37.                 {
  38.                     if (!item.IsWriteEnabled)
  39.                         item.UpgradeOpen();
  40.                 }
  41.                 catch (Autodesk.AutoCAD.Runtime.Exception ex)
  42.                 {
  43.                     if (ex.ErrorStatus != ErrorStatus.OnLockedLayer)
  44.                         throw;
  45.                     LayerTableRecord ltr = (LayerTableRecord)item.LayerId.GetObject(OpenMode.ForWrite);
  46.                     ltr.IsLocked = false;
  47.                     lockedLayers.Add(ltr);
  48.                     item.UpgradeOpen();
  49.                 }
  50.                 yield return item;
  51.             }
  52.             foreach (var layer in lockedLayers)
  53.             {
  54.                 layer.IsLocked = true;
  55.             }
  56.         }
  57.  
« Last Edit: June 03, 2012, 07:14:07 PM by TheMaster »

Jeff H

  • Needs a day job
  • Posts: 6150
Re: Opening entites on locked layer
« Reply #6 on: June 03, 2012, 08:21:39 PM »
For some reason lately we have been receiving files where the entities in 'BlockDefinitions' are on locked layers. Usually batch through them and set all definitions to 0 and ByLayer.

TheMaster

  • Guest
Re: Opening entites on locked layer
« Reply #7 on: June 03, 2012, 08:27:48 PM »

Code - C#: [Select]
  1.  
  2.          public static IEnumerable<T> UpgradeOpen<T>(this IEnumerable<T> source, bool openObjectsOnLockedLayers)
  3. where T : Entity
  4.         {
  5.             bool isOnLockedlayer = true;
  6.             foreach (T item in source)
  7.             {
  8.                 try
  9.                 {
  10.                     if (!item.IsWriteEnabled)
  11.                         item.UpgradeOpen();
  12.                     isOnLockedlayer = false;
  13.                 }
  14.                 catch (Autodesk.AutoCAD.Runtime.Exception ex)
  15.                 {
  16.                     if (ex.ErrorStatus != ErrorStatus.OnLockedLayer)
  17.                         throw;
  18.                     isOnLockedlayer = true;
  19.                 }
  20.                 if (isOnLockedlayer && openObjectsOnLockedLayers)
  21.                 {                
  22.                     yield return (T)item.ObjectId.GetObject(OpenMode.ForWrite, true, true);
  23.                 }
  24.                 if (!item.IsWriteEnabled)
  25.                     continue;
  26.                 yield return item;
  27.             }
  28.         }
  29.  
  30.         public static IEnumerable<T> UpgradeOpenEvenLocked<T>(this IEnumerable<T> source, bool openObjectsOnLockedLayers)
  31. where T : Entity
  32.         {
  33.             List<LayerTableRecord> lockedLayers = new List<LayerTableRecord>();
  34.             foreach (T item in source)
  35.             {
  36.                 try
  37.                 {
  38.                     if (!item.IsWriteEnabled)
  39.                         item.UpgradeOpen();
  40.                 }
  41.                 catch (Autodesk.AutoCAD.Runtime.Exception ex)
  42.                 {
  43.                     if (ex.ErrorStatus != ErrorStatus.OnLockedLayer)
  44.                         throw;
  45.                     LayerTableRecord ltr = (LayerTableRecord)item.LayerId.GetObject(OpenMode.ForWrite);
  46.                     ltr.IsLocked = false;
  47.                     lockedLayers.Add(ltr);
  48.                     item.UpgradeOpen();
  49.                 }
  50.                 yield return item;
  51.             }
  52.             foreach (var layer in lockedLayers)
  53.             {
  54.                 layer.IsLocked = true;
  55.             }
  56.         }
  57.  

Looking at your derivatives more closely, and assuming that you want to open objects on locked layers for write, the second one that unlocks and locks the layers will be a problem because the when a caller uses a foreach() loop to iterate over the items, but breaks out of the loop before the end of the sequence is reached, code that follows the foreach() loop that contains the yield return may not run. I've found that I need to put code that should run after items have been yielded in a finally{} block, to ensure that code will always run, including when a calling foreach() loop is exited as a result of using break, continue or return, or an exception is thrown.


TheMaster

  • Guest
Re: Opening entites on locked layer
« Reply #8 on: June 03, 2012, 11:32:10 PM »
For some reason lately we have been receiving files where the entities in 'BlockDefinitions' are on locked layers. Usually batch through them and set all definitions to 0 and ByLayer.

For that type of thing, I would just open objects for read, and if they need to be changed, reopen them for write with GetObject(). There's more memory overhead to that because it creates two managed wrappers for each object that's opened for write after being opened for read. You can minimize the memory overhead by passing one of the two managed wrappers to Interop.DetachUnmanagedObject() and then passing the same wrapper to GC.SupressFinalize() to prevent the finalizer from running.

I'm still not sure what benefit there is to unlocking and then locking layers verses using GetObject() with the forceOpenOnLockedLayer argument set to true.
« Last Edit: June 04, 2012, 12:23:27 AM by TheMaster »

Jeff H

  • Needs a day job
  • Posts: 6150
Re: Opening entites on locked layer
« Reply #9 on: June 04, 2012, 02:03:18 PM »
Thanks,
Tony