Author Topic: Using Hard pointers to manage layers  (Read 7683 times)

0 Members and 1 Guest are viewing this topic.

sovby

  • Guest
Using Hard pointers to manage layers
« on: April 21, 2016, 10:57:12 PM »
I have a user at my office who purges Autocad drawings with layers that i want to keep in the drawings. Someone in the Augi forum suggested using hard pointers to keep the layers from being removed. Has anyone done anything similar to this? I do not have much experience using xrecords. Here is a sample from the forum post. "A hard pointer should prevent the pointed-to object (layer, in this case) from being purged. The pointer would reside under an XRecord in an Extension Dictionary, possibly attached to the Layers collection or the drawings named object dictionary." Would this work & if so how would i go about doing this?

gile

  • Gator
  • Posts: 2507
  • Marseille, France
Re: Using Hard pointers to manage layers
« Reply #1 on: April 22, 2016, 02:15:32 AM »
Hi,

You can try these extension methods I wrote some time ago.

Code - C#: [Select]
  1.     public static class Extensions
  2.     {
  3.         // Opens the object for read.
  4.         public static T GetObject<T>(this ObjectId id) where T : DBObject
  5.         {
  6.             return (T)id.GetObject(OpenMode.ForRead, false, false);
  7.         }
  8.  
  9.         // Opens the object in the specified mode
  10.         public static T GetObject<T>(this ObjectId id, OpenMode mode) where T : DBObject
  11.         {
  12.             return (T)id.GetObject(mode, false, false);
  13.         }
  14.  
  15.         // Gets the object extension dictionary (returns null if not found)
  16.         public static DBDictionary GetExtensionDictionary(this DBObject obj)
  17.         {
  18.             ObjectId dictId = obj.ExtensionDictionary;
  19.             if (dictId == ObjectId.Null)
  20.             {
  21.                 return null;
  22.             }
  23.             return dictId.GetObject<DBDictionary>();
  24.         }
  25.  
  26.         // Gets the object extension dictionary (creates it if not already exists)
  27.         public static DBDictionary GetOrCreateExtensionDictionary(this DBObject obj)
  28.         {
  29.             if (obj.ExtensionDictionary == ObjectId.Null)
  30.             {
  31.                 obj.UpgradeOpen();
  32.                 obj.CreateExtensionDictionary();
  33.             }
  34.             return obj.ExtensionDictionary.GetObject<DBDictionary>();
  35.         }
  36.  
  37.         // Gets the xrecord data
  38.         public static ResultBuffer GetXrecordData(this DBDictionary dict, string key)
  39.         {
  40.             if (!dict.Contains(key)) return null;
  41.             ObjectId id = (ObjectId)dict[key];
  42.             if (id.ObjectClass != RXObject.GetClass(typeof(Xrecord))) return null;
  43.             return id.GetObject<Xrecord>().Data;
  44.         }
  45.  
  46.         // Sets the xrecord data
  47.         public static void SetXrecordData(this DBDictionary dict, string key, ResultBuffer data)
  48.         {
  49.             Xrecord xrec;
  50.             if (dict.Contains(key))
  51.             {
  52.                 xrec = ((ObjectId)dict[key]).GetObject<Xrecord>(OpenMode.ForWrite);
  53.             }
  54.             else
  55.             {
  56.                 dict.UpgradeOpen();
  57.                 xrec = new Xrecord();
  58.                 dict.SetAt(key, xrec);
  59.                 dict.Database.TransactionManager.TopTransaction.AddNewlyCreatedDBObject(xrec, true);
  60.             }
  61.             xrec.Data = data;
  62.         }
  63.  
  64.         // Sets the symbol table record unpurgeable by referencing it in its symbol table extension dictionary
  65.         public static void SetUnpurgeable(this SymbolTableRecord str)
  66.         {
  67.             SymbolTable st = str.OwnerId.GetObject<SymbolTable>();
  68.             DBDictionary xdict = st.GetOrCreateExtensionDictionary();
  69.             ResultBuffer data = xdict.GetXrecordData("GILE_UNPURGE") ?? new ResultBuffer();
  70.             data.Add(new TypedValue(340, str.ObjectId));
  71.             xdict.SetXrecordData("GILE_UNPURGE", data);
  72.         }
  73.  
  74.         // Resets the symbol table record purgeable by unreferencing it in its symbol table extension dictionary
  75.         public static void ResetPurgeable(this SymbolTableRecord str)
  76.         {
  77.             SymbolTable st = str.OwnerId.GetObject<SymbolTable>();
  78.             DBDictionary xdict = st.GetExtensionDictionary();
  79.             if (xdict == null) return;
  80.             ResultBuffer data = xdict.GetXrecordData("GILE_UNPURGE");
  81.             if (data == null) return;
  82.             var values = data.AsArray()
  83.                 .Where(tv => tv.TypeCode != 340 || (ObjectId)tv.Value != st.ObjectId)
  84.                 .ToArray();
  85.             xdict.SetXrecordData("GILE_UNPURGE", new ResultBuffer(values));
  86.         }
  87.     }

Using example, adds a new layer named "unpurgeable" to the layer table and set it 'unpurgeable':

Code - C#: [Select]
  1.             using (Transaction tr = db.TransactionManager.StartTransaction())
  2.             {
  3.                 LayerTable lt = db.LayerTableId.GetObject<LayerTable>(OpenMode.ForWrite);
  4.                 LayerTableRecord ltr = new LayerTableRecord();
  5.                 ltr.Name = "unpurgeable";
  6.                 lt.Add(ltr);
  7.                 tr.AddNewlyCreatedDBObject(ltr, true);
  8.                 ltr.SetUnpurgeable();
  9.                 tr.Commit();
  10.             }
Speaking English as a French Frog

sovby

  • Guest
Re: Using Hard pointers to manage layers
« Reply #2 on: April 22, 2016, 06:47:18 AM »
Sorry for my lack of knowledge on this but do i combine these two together & add my specific layers in. There's roughly 300 of them. Also how would i implement this? Do i need to compile this & turn it into a dll file or something?

gile

  • Gator
  • Posts: 2507
  • Marseille, France
Re: Using Hard pointers to manage layers
« Reply #3 on: April 22, 2016, 07:01:58 AM »
Extension methods are used to add methods to a predefined type (see this page).

If you add the upper "Extensions" class to your project, those method will be available for the extended type (for your purpose, the SetUnpurgeable() method extents the SymbolTableRecord abstract type which LayerTableRecord derived from).
So, in you code, open each LayerTableRecord instance you want to set unpurgeable and call the SetUnpurgeable method on it.
Speaking English as a French Frog

MexicanCustard

  • Swamp Rat
  • Posts: 705
Re: Using Hard pointers to manage layers
« Reply #4 on: April 22, 2016, 07:40:38 AM »
Gile, why wrap ObjectId.GetObject() with a GetObject method?  I think it was Tony T that pointed out the inefficiencies of ObjectId.GetObject().  Just asking, since I know you'll have a good reason.
Revit 2019, AMEP 2019 64bit Win 10

gile

  • Gator
  • Posts: 2507
  • Marseille, France
Re: Using Hard pointers to manage layers
« Reply #5 on: April 22, 2016, 08:41:16 AM »
Hi,

This is an old (short) version.

Here's the one I use now:

Code - C#: [Select]
  1.         public static T GetObject<T>(
  2.             this ObjectId id,
  3.             OpenMode mode = OpenMode.ForRead,
  4.             bool openErased = false,
  5.             bool forceOpenOnLockedLayer = false)
  6.             where T : DBObject
  7.         {
  8.             if (id == ObjectId.Null)
  9.                 throw new ArgumentException("The value cannot be ObjectId.Null.", "id");
  10.  
  11.             Transaction tr = id.Database.TransactionManager.TopTransaction;
  12.             if (tr == null)
  13.                 throw new AcRx.Exception(ErrorStatus.NoActiveTransactions);
  14.  
  15.             return (T)tr.GetObject(id, mode, openErased, forceOpenOnLockedLayer);
  16.         }

Anyway, even if ObjectId.GetObject() is (a little) less efficient than Transaction.GetOject() (which is less efficient than OpenCloseTransaction.GetObject() or ObjectId.Open()), the goal here was more to get a succint and readable code.
« Last Edit: April 23, 2016, 02:01:51 AM by gile »
Speaking English as a French Frog

MexicanCustard

  • Swamp Rat
  • Posts: 705
Re: Using Hard pointers to manage layers
« Reply #6 on: April 22, 2016, 01:21:26 PM »
Quote
Anyway, even if ObjectId.GetObject() is (a little) less efficient than Transaction.GetOject() (which is less efficient than OpenCloseTransaction.GetObject() or ObjectId.Open()), the goal here was more to get a succint and readable code.

Just to be clear, I wasn't picking holes in your code.  I understand you were just showing the OP an example.  I thought maybe you knew something I didn't, since your knowledge of the API is better than mine.  Thank you for clarifying though.
Revit 2019, AMEP 2019 64bit Win 10

gile

  • Gator
  • Posts: 2507
  • Marseille, France
Re: Using Hard pointers to manage layers
« Reply #7 on: April 22, 2016, 01:48:50 PM »
Quote
since your knowledge of the API is better than mine.
I'm thinking the same about you.
Speaking English as a French Frog

sovby

  • Guest
Re: Using Hard pointers to manage layers
« Reply #8 on: April 22, 2016, 04:44:31 PM »
Extension methods are used to add methods to a predefined type (see this page).

If you add the upper "Extensions" class to your project, those method will be available for the extended type (for your purpose, the SetUnpurgeable() method extents the SymbolTableRecord abstract type which LayerTableRecord derived from).
So, in you code, open each LayerTableRecord instance you want to set unpurgeable and call the SetUnpurgeable method on it.
Sorry i'm such a dummy on this but if i understand you correctly i need to add all my layers into this code like layer1,layer2,layer3,etc. Also i am not sure how to implement this. Do i compile this into a dll file? This is my template file. i want all of these layers to be unpurgeable. Do i need to run this code in side of this file?

mjfarrell

  • Seagull
  • Posts: 14444
  • Every Student their own Lesson
Re: Using Hard pointers to manage layers
« Reply #9 on: April 22, 2016, 05:01:41 PM »
might it not be easier to tell people to STOP purging layers?

and or

frustrate the bugger....place a single autocad point on those layers...then he can't purge them...pdmode=1

they wont even be able to see them.... :whistling:
Be your Best


Michael Farrell
http://primeservicesglobal.com/

gile

  • Gator
  • Posts: 2507
  • Marseille, France
Re: Using Hard pointers to manage layers
« Reply #10 on: April 22, 2016, 06:13:18 PM »
sovby,

Yes, you can compile the extension class in a dll, this is a good way to build a re-usable library, but in this case it will be a very small library...

You can also, as I said, just add this class to the project which use it, even in a separate file or in the same file as the command method which use it as shown in the following example.

Code - C#: [Select]
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Runtime;
  5. using System.Linq;
  6. using AcAp = Autodesk.AutoCAD.ApplicationServices.Application;
  7.  
  8. [assembly: CommandClass(typeof(SetAllLayersUnpurgeable.Commands))]
  9.  
  10. namespace SetAllLayersUnpurgeable
  11. {
  12.     public class Commands
  13.     {
  14.         [CommandMethod("Cmd")]
  15.         public void SetAllLayersUnpurgeableCmd()
  16.         {
  17.             var db = HostApplicationServices.WorkingDatabase;
  18.             using (var tr = db.TransactionManager.StartTransaction())
  19.             {
  20.                 var layerTable = db.LayerTableId.GetObject<LayerTable>();
  21.                 foreach (ObjectId layerId in layerTable)
  22.                 {
  23.                     if (layerId != db.LayerZero)
  24.                     {
  25.                         layerId.GetObject<LayerTableRecord>().SetUnpurgeable();
  26.                     }
  27.                 }
  28.                 tr.Commit();
  29.             }
  30.         }
  31.     }
  32.  
  33.     public static class Extensions
  34.     {
  35.         // Opens the object for read.
  36.         public static T GetObject<T>(this ObjectId id) where T : DBObject
  37.         {
  38.             return (T)id.GetObject(OpenMode.ForRead, false, false);
  39.         }
  40.  
  41.         // Opens the object in the specified mode
  42.         public static T GetObject<T>(this ObjectId id, OpenMode mode) where T : DBObject
  43.         {
  44.             return (T)id.GetObject(mode, false, false);
  45.         }
  46.  
  47.         // Gets the object extension dictionary (returns null if not found)
  48.         public static DBDictionary GetExtensionDictionary(this DBObject obj)
  49.         {
  50.             ObjectId dictId = obj.ExtensionDictionary;
  51.             if (dictId == ObjectId.Null)
  52.             {
  53.                 return null;
  54.             }
  55.             return dictId.GetObject<DBDictionary>();
  56.         }
  57.  
  58.         // Gets the object extension dictionary (creates it if not already exists)
  59.         public static DBDictionary GetOrCreateExtensionDictionary(this DBObject obj)
  60.         {
  61.             if (obj.ExtensionDictionary == ObjectId.Null)
  62.             {
  63.                 obj.UpgradeOpen();
  64.                 obj.CreateExtensionDictionary();
  65.             }
  66.             return obj.ExtensionDictionary.GetObject<DBDictionary>();
  67.         }
  68.  
  69.         // Gets the xrecord data
  70.         public static ResultBuffer GetXrecordData(this DBDictionary dict, string key)
  71.         {
  72.             if (!dict.Contains(key)) return null;
  73.             ObjectId id = (ObjectId)dict[key];
  74.             if (id.ObjectClass != RXObject.GetClass(typeof(Xrecord))) return null;
  75.             return id.GetObject<Xrecord>().Data;
  76.         }
  77.  
  78.         // Sets the xrecord data
  79.         public static void SetXrecordData(this DBDictionary dict, string key, ResultBuffer data)
  80.         {
  81.             Xrecord xrec;
  82.             if (dict.Contains(key))
  83.             {
  84.                 xrec = ((ObjectId)dict[key]).GetObject<Xrecord>(OpenMode.ForWrite);
  85.             }
  86.             else
  87.             {
  88.                 dict.UpgradeOpen();
  89.                 xrec = new Xrecord();
  90.                 dict.SetAt(key, xrec);
  91.                 dict.Database.TransactionManager.TopTransaction.AddNewlyCreatedDBObject(xrec, true);
  92.             }
  93.             xrec.Data = data;
  94.         }
  95.  
  96.         // Sets the symbol table record unpurgeable by referencing it in its symbol table extension dictionary
  97.         public static void SetUnpurgeable(this SymbolTableRecord str)
  98.         {
  99.             SymbolTable st = str.OwnerId.GetObject<SymbolTable>();
  100.             DBDictionary xdict = st.GetOrCreateExtensionDictionary();
  101.             ResultBuffer data = xdict.GetXrecordData("GILE_UNPURGE") ?? new ResultBuffer();
  102.             data.Add(new TypedValue(340, str.ObjectId));
  103.             xdict.SetXrecordData("GILE_UNPURGE", data);
  104.         }
  105.  
  106.         // Resets the symbol table record purgeable by unreferencing it in its symbol table extension dictionary
  107.         public static void ResetPurgeable(this SymbolTableRecord str)
  108.         {
  109.             SymbolTable st = str.OwnerId.GetObject<SymbolTable>();
  110.             DBDictionary xdict = st.GetExtensionDictionary();
  111.             if (xdict == null) return;
  112.             ResultBuffer data = xdict.GetXrecordData("GILE_UNPURGE");
  113.             if (data == null) return;
  114.             var values = data.AsArray()
  115.                 .Where(tv => tv.TypeCode != 340 || (ObjectId)tv.Value != st.ObjectId)
  116.                 .ToArray();
  117.             xdict.SetXrecordData("GILE_UNPURGE", new ResultBuffer(values));
  118.         }
  119.     }
  120. }
  121.  

mjfarrell,
Quote
frustrate the bugger....place a single autocad point on those layers...then he can't purge them...pdmode=1
This is what sovby did in the attached file. Anyone can set PDMODE to 3 (or whatever else to display the points), erase the points (within a block reference here) and purge all.
Speaking English as a French Frog

dgorsman

  • Water Moccasin
  • Posts: 2437
Re: Using Hard pointers to manage layers
« Reply #11 on: April 22, 2016, 06:20:33 PM »
Well yeah, its always easier to tell them the right way.  They bob their head and reply "Yes.  Yes.  Yes." and promptly go back to doing what they think is right.

There can be other, non-user related complications.  We have some tools from one client which automatically purges everything every time a drawing is opened.
If you are going to fly by the seat of your pants, expect friction burns.

try {GreatPower;}
   catch (notResponsible)
      {NextTime(PlanAhead);}
   finally
      {MasterBasics;}

Jeff H

  • Needs a day job
  • Posts: 6150
Re: Using Hard pointers to manage layers
« Reply #12 on: April 23, 2016, 12:46:40 AM »

sovby

  • Guest
Re: Using Hard pointers to manage layers
« Reply #13 on: April 23, 2016, 01:37:29 PM »
might it not be easier to tell people to STOP purging layers?

and or

frustrate the bugger....place a single autocad point on those layers...then he can't purge them...pdmode=1

they wont even be able to see them.... :whistling:
Yeah it would be but in my case i don't think it would work. My solution was to create a block with points on every layer which i made small & put it in the bottom left corner but they found it & deleted the block. I guess i didn't hide it well enough.

sovby

  • Guest
Re: Using Hard pointers to manage layers
« Reply #14 on: April 23, 2016, 01:55:10 PM »
sovby,

Yes, you can compile the extension class in a dll, this is a good way to build a re-usable library, but in this case it will be a very small library...

You can also, as I said, just add this class to the project which use it, even in a separate file or in the same file as the command method which use it as shown in the following example.

Code - C#: [Select]
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Runtime;
  5. using System.Linq;
  6. using AcAp = Autodesk.AutoCAD.ApplicationServices.Application;
  7.  
  8. [assembly: CommandClass(typeof(SetAllLayersUnpurgeable.Commands))]
  9.  
  10. namespace SetAllLayersUnpurgeable
  11. {
  12.     public class Commands
  13.     {
  14.         [CommandMethod("Cmd")]
  15.         public void SetAllLayersUnpurgeableCmd()
  16.         {
  17.             var db = HostApplicationServices.WorkingDatabase;
  18.             using (var tr = db.TransactionManager.StartTransaction())
  19.             {
  20.                 var layerTable = db.LayerTableId.GetObject<LayerTable>();
  21.                 foreach (ObjectId layerId in layerTable)
  22.                 {
  23.                     if (layerId != db.LayerZero)
  24.                     {
  25.                         layerId.GetObject<LayerTableRecord>().SetUnpurgeable();
  26.                     }
  27.                 }
  28.                 tr.Commit();
  29.             }
  30.         }
  31.     }
  32.  
  33.     public static class Extensions
  34.     {
  35.         // Opens the object for read.
  36.         public static T GetObject<T>(this ObjectId id) where T : DBObject
  37.         {
  38.             return (T)id.GetObject(OpenMode.ForRead, false, false);
  39.         }
  40.  
  41.         // Opens the object in the specified mode
  42.         public static T GetObject<T>(this ObjectId id, OpenMode mode) where T : DBObject
  43.         {
  44.             return (T)id.GetObject(mode, false, false);
  45.         }
  46.  
  47.         // Gets the object extension dictionary (returns null if not found)
  48.         public static DBDictionary GetExtensionDictionary(this DBObject obj)
  49.         {
  50.             ObjectId dictId = obj.ExtensionDictionary;
  51.             if (dictId == ObjectId.Null)
  52.             {
  53.                 return null;
  54.             }
  55.             return dictId.GetObject<DBDictionary>();
  56.         }
  57.  
  58.         // Gets the object extension dictionary (creates it if not already exists)
  59.         public static DBDictionary GetOrCreateExtensionDictionary(this DBObject obj)
  60.         {
  61.             if (obj.ExtensionDictionary == ObjectId.Null)
  62.             {
  63.                 obj.UpgradeOpen();
  64.                 obj.CreateExtensionDictionary();
  65.             }
  66.             return obj.ExtensionDictionary.GetObject<DBDictionary>();
  67.         }
  68.  
  69.         // Gets the xrecord data
  70.         public static ResultBuffer GetXrecordData(this DBDictionary dict, string key)
  71.         {
  72.             if (!dict.Contains(key)) return null;
  73.             ObjectId id = (ObjectId)dict[key];
  74.             if (id.ObjectClass != RXObject.GetClass(typeof(Xrecord))) return null;
  75.             return id.GetObject<Xrecord>().Data;
  76.         }
  77.  
  78.         // Sets the xrecord data
  79.         public static void SetXrecordData(this DBDictionary dict, string key, ResultBuffer data)
  80.         {
  81.             Xrecord xrec;
  82.             if (dict.Contains(key))
  83.             {
  84.                 xrec = ((ObjectId)dict[key]).GetObject<Xrecord>(OpenMode.ForWrite);
  85.             }
  86.             else
  87.             {
  88.                 dict.UpgradeOpen();
  89.                 xrec = new Xrecord();
  90.                 dict.SetAt(key, xrec);
  91.                 dict.Database.TransactionManager.TopTransaction.AddNewlyCreatedDBObject(xrec, true);
  92.             }
  93.             xrec.Data = data;
  94.         }
  95.  
  96.         // Sets the symbol table record unpurgeable by referencing it in its symbol table extension dictionary
  97.         public static void SetUnpurgeable(this SymbolTableRecord str)
  98.         {
  99.             SymbolTable st = str.OwnerId.GetObject<SymbolTable>();
  100.             DBDictionary xdict = st.GetOrCreateExtensionDictionary();
  101.             ResultBuffer data = xdict.GetXrecordData("GILE_UNPURGE") ?? new ResultBuffer();
  102.             data.Add(new TypedValue(340, str.ObjectId));
  103.             xdict.SetXrecordData("GILE_UNPURGE", data);
  104.         }
  105.  
  106.         // Resets the symbol table record purgeable by unreferencing it in its symbol table extension dictionary
  107.         public static void ResetPurgeable(this SymbolTableRecord str)
  108.         {
  109.             SymbolTable st = str.OwnerId.GetObject<SymbolTable>();
  110.             DBDictionary xdict = st.GetExtensionDictionary();
  111.             if (xdict == null) return;
  112.             ResultBuffer data = xdict.GetXrecordData("GILE_UNPURGE");
  113.             if (data == null) return;
  114.             var values = data.AsArray()
  115.                 .Where(tv => tv.TypeCode != 340 || (ObjectId)tv.Value != st.ObjectId)
  116.                 .ToArray();
  117.             xdict.SetXrecordData("GILE_UNPURGE", new ResultBuffer(values));
  118.         }
  119.     }
  120. }
  121.  

mjfarrell,
Quote
frustrate the bugger....place a single autocad point on those layers...then he can't purge them...pdmode=1
This is what sovby did in the attached file. Anyone can set PDMODE to 3 (or whatever else to display the points), erase the points (within a block reference here) and purge all.
I really apologize for asking this but i feel like i might need step by step directions for this. I have visual studio so would i use that to copy this code into or just notepad? What kind of file would i save it to? How do i get it to talk with Autocad? Again, i am very sorry but i have never done any thing like this before with Autocad. I have gotten a little bit more familiar with c# language but i am lost as to what the procedure would be to make this happen. Does this look at the file that i posted to get the layers or do i have to add them all in manually into this code? If so where exactly do i need to do this? You said to add the class to the projects that use it but i dont know what you mean. How do i do this?