Hi forums,
Is there a method to store a pair of data values (dictionary < key, list<objectid>>) in once key in the Xrecords of an entity?
Thank you.
Solved! Go to Solution.
Hi forums,
Is there a method to store a pair of data values (dictionary < key, list<objectid>>) in once key in the Xrecords of an entity?
Thank you.
Solved! Go to Solution.
Solved by ActivistInvestor. Go to Solution.
Xrecord's Data is a ResultBuffer, which is an array of TypedValue. So, you can have as many as TypedValues in the ResultBuffer (Xrecord.Data). To store a list of ObjectId, you do:
var values=from id in TheObjectIdList select new TypedValue((int)DxfCode.SoftPointerId, id);
var xrec = new Xrecord();
xrec.Data=new ResultBuffer(values.ToArray());
tran.AddNewlyCreatedObject(xrec, true);
TheDict.SetAt("theKey", xrec);
Norman Yuan
Xrecord's Data is a ResultBuffer, which is an array of TypedValue. So, you can have as many as TypedValues in the ResultBuffer (Xrecord.Data). To store a list of ObjectId, you do:
var values=from id in TheObjectIdList select new TypedValue((int)DxfCode.SoftPointerId, id);
var xrec = new Xrecord();
xrec.Data=new ResultBuffer(values.ToArray());
tran.AddNewlyCreatedObject(xrec, true);
TheDict.SetAt("theKey", xrec);
Norman Yuan
Xrecords can't store managed types like dictionaries directly, but you can store the keys and values as individual items. You could use a few helper methods like these:
public static class DictionaryToResultBufferExtensions
{
public static ResultBuffer ToResultBuffer<TKey, TValue>(this Dictionary<TKey, TValue> items, short keyCode, short valueCode)
{
var result = new ResultBuffer();
foreach(var pair in items)
{
result.Add(new TypedValue(keyCode, pair.Key));
result.Add(new TypedValue(valueCode, pair.Value));
}
return result;
}
public static Dictionary<TKey, TValue> ToDictionary<TKey, TValue>(this ResultBuffer rb)
{
var result = new Dictionary<TKey,TValue>();
var e = rb.GetEnumerator();
while(e.MoveNext())
{
var key = e.Current.Value;
if(!e.MoveNext())
throw new InvalidOperationException("Count mismatch");
result.Add((TKey)key, (TValue)e.Current.Value);
}
return result;
}
}
public static class Example
{
[CommandMethod("CONVERTDEMO")]
public static void Run()
{
Dictionary<string, double> map = new Dictionary<string, double>();
map["First"] = 100.0;
map["Second"] = 200.0;
map["Third"] = 300.0;
// Convert to ResultBuffer
ResultBuffer rb = map.ToResultBuffer((short) DxfCode.Text, (short) DxfCode.Real);
Write("\nResultBuffer: ");
foreach(TypedValue tv in rb)
{
Write($"TypeCode: {tv.TypeCode} Value: {tv.Value}");
}
// Convert back to Dictionary<string, double>
map = rb.ToDictionary<string, double>();
Write("\nDictionary: ");
foreach(var pair in map)
{
Write($"{pair.Key} = {pair.Value}");
}
static void Write(string fmt, params object[] args)
{
Application.DocumentManager.MdiActiveDocument?
.Editor.WriteMessage("\n" + fmt, args);
}
}
}
Xrecords can't store managed types like dictionaries directly, but you can store the keys and values as individual items. You could use a few helper methods like these:
public static class DictionaryToResultBufferExtensions
{
public static ResultBuffer ToResultBuffer<TKey, TValue>(this Dictionary<TKey, TValue> items, short keyCode, short valueCode)
{
var result = new ResultBuffer();
foreach(var pair in items)
{
result.Add(new TypedValue(keyCode, pair.Key));
result.Add(new TypedValue(valueCode, pair.Value));
}
return result;
}
public static Dictionary<TKey, TValue> ToDictionary<TKey, TValue>(this ResultBuffer rb)
{
var result = new Dictionary<TKey,TValue>();
var e = rb.GetEnumerator();
while(e.MoveNext())
{
var key = e.Current.Value;
if(!e.MoveNext())
throw new InvalidOperationException("Count mismatch");
result.Add((TKey)key, (TValue)e.Current.Value);
}
return result;
}
}
public static class Example
{
[CommandMethod("CONVERTDEMO")]
public static void Run()
{
Dictionary<string, double> map = new Dictionary<string, double>();
map["First"] = 100.0;
map["Second"] = 200.0;
map["Third"] = 300.0;
// Convert to ResultBuffer
ResultBuffer rb = map.ToResultBuffer((short) DxfCode.Text, (short) DxfCode.Real);
Write("\nResultBuffer: ");
foreach(TypedValue tv in rb)
{
Write($"TypeCode: {tv.TypeCode} Value: {tv.Value}");
}
// Convert back to Dictionary<string, double>
map = rb.ToDictionary<string, double>();
Write("\nDictionary: ");
foreach(var pair in map)
{
Write($"{pair.Key} = {pair.Value}");
}
static void Write(string fmt, params object[] args)
{
Application.DocumentManager.MdiActiveDocument?
.Editor.WriteMessage("\n" + fmt, args);
}
}
}
I'm sorry I didn't provide enough information about my case. In fact, I want to store in the NOD the ObjectID of the "parent" (entity 1 in the pic below) and the list of ObjectIDs of the "children" (entities 2 and 3). This relationship is stored as a dictionary: Dictionary<ObjectId Parent, List<ObjectId> children>. This allows me to access all entities related to a given parent afterwards and check if already exist (ContainsKey(TKey) Method). Since I have multiple relationships of this type in my design.
The issue I'm encountering is storing this architecture at the Xrecords level.
I tried to implement an idea: I store all entities sequentially in an array of TypedValue, using a string separator "||" between each parent.
ResultBuffer rsbf = new ResultBuffer(new TypedValue((int)DxfCode.SoftPointerId, ent1.ObjectId),
new TypedValue((int)DxfCode.SoftPointerId, ent2.ObjectId),
new TypedValue((int)DxfCode.SoftPointerId, ent3.ObjectId),
new TypedValue((int)DxfCode.Text, "||"), // separator
new TypedValue((int)DxfCode.SoftPointerId, ent1.ObjectId),
new TypedValue((int)DxfCode.SoftPointerId, ent2.ObjectId),
new TypedValue((int)DxfCode.SoftPointerId, ent3.ObjectId)
);
try
{
// Find the NOD in the database
DBDictionary nod = (DBDictionary)tr.GetObject(doc.Database.NamedObjectsDictionaryId, OpenMode.ForWrite);
// We use Xrecord class to store data in Dictionaries
if (nod.Contains(key)) // existing key
{
xRec = (Xrecord)tr.GetObject(nod.GetAt(key), OpenMode.ForWrite, false);
xRec.Data = rsbf;
}
else // new element
{
xRec.Data = rsbf;
nod.SetAt(key, xRec);
tr.AddNewlyCreatedDBObject(xRec, true);
}
}
catch
{
doc.Editor.WriteMessage("\nError");
}
Read informations :
try
{
DBDictionary nod = (DBDictionary)tr.GetObject(doc.Database.NamedObjectsDictionaryId, OpenMode.ForRead);
Xrecord readBack = (Xrecord)tr.GetObject(nod.GetAt(key), OpenMode.ForRead);
ResultBuffer rs = readBack.Data;
if (rs != null)
{
TypedValue[] result = rs.AsArray();
bool isStartItem = true;
for (int i = 0; i < result.Length; i++)
{
if (result[i].TypeCode == (int)DxfCode.SoftPointerId)
{
if (isStartItem)
{
ed.WriteMessage("\nParent element : " + (ObjectId)result[i].Value);
isStartItem = false;
}
else
ed.WriteMessage("\nChild element : " + (ObjectId)result[i].Value);
}
else if (result[i].TypeCode == (int)DxfCode.Text)
isStartItem = true;
}
}
}
catch
{
doc.Editor.WriteMessage("\nError");
}
I'm sorry I didn't provide enough information about my case. In fact, I want to store in the NOD the ObjectID of the "parent" (entity 1 in the pic below) and the list of ObjectIDs of the "children" (entities 2 and 3). This relationship is stored as a dictionary: Dictionary<ObjectId Parent, List<ObjectId> children>. This allows me to access all entities related to a given parent afterwards and check if already exist (ContainsKey(TKey) Method). Since I have multiple relationships of this type in my design.
The issue I'm encountering is storing this architecture at the Xrecords level.
I tried to implement an idea: I store all entities sequentially in an array of TypedValue, using a string separator "||" between each parent.
ResultBuffer rsbf = new ResultBuffer(new TypedValue((int)DxfCode.SoftPointerId, ent1.ObjectId),
new TypedValue((int)DxfCode.SoftPointerId, ent2.ObjectId),
new TypedValue((int)DxfCode.SoftPointerId, ent3.ObjectId),
new TypedValue((int)DxfCode.Text, "||"), // separator
new TypedValue((int)DxfCode.SoftPointerId, ent1.ObjectId),
new TypedValue((int)DxfCode.SoftPointerId, ent2.ObjectId),
new TypedValue((int)DxfCode.SoftPointerId, ent3.ObjectId)
);
try
{
// Find the NOD in the database
DBDictionary nod = (DBDictionary)tr.GetObject(doc.Database.NamedObjectsDictionaryId, OpenMode.ForWrite);
// We use Xrecord class to store data in Dictionaries
if (nod.Contains(key)) // existing key
{
xRec = (Xrecord)tr.GetObject(nod.GetAt(key), OpenMode.ForWrite, false);
xRec.Data = rsbf;
}
else // new element
{
xRec.Data = rsbf;
nod.SetAt(key, xRec);
tr.AddNewlyCreatedDBObject(xRec, true);
}
}
catch
{
doc.Editor.WriteMessage("\nError");
}
Read informations :
try
{
DBDictionary nod = (DBDictionary)tr.GetObject(doc.Database.NamedObjectsDictionaryId, OpenMode.ForRead);
Xrecord readBack = (Xrecord)tr.GetObject(nod.GetAt(key), OpenMode.ForRead);
ResultBuffer rs = readBack.Data;
if (rs != null)
{
TypedValue[] result = rs.AsArray();
bool isStartItem = true;
for (int i = 0; i < result.Length; i++)
{
if (result[i].TypeCode == (int)DxfCode.SoftPointerId)
{
if (isStartItem)
{
ed.WriteMessage("\nParent element : " + (ObjectId)result[i].Value);
isStartItem = false;
}
else
ed.WriteMessage("\nChild element : " + (ObjectId)result[i].Value);
}
else if (result[i].TypeCode == (int)DxfCode.Text)
isStartItem = true;
}
}
}
catch
{
doc.Editor.WriteMessage("\nError");
}
Thank you @ActivistInvestor for your response. Actually, in my case, the second TValue element is a list of ObjectIDs, which makes things more complex.
Thank you @ActivistInvestor for your response. Actually, in my case, the second TValue element is a list of ObjectIDs, which makes things more complex.
Thank you @norman.yuan for your reply.
Indeed, I don't have a simple list of ObjectIDs, but rather a relationship between one element and several other elements. That's why I'm looking to store this information in the form of a dictionary.
Thank you @norman.yuan for your reply.
Indeed, I don't have a simple list of ObjectIDs, but rather a relationship between one element and several other elements. That's why I'm looking to store this information in the form of a dictionary.
@youssefGC wrote:Thank you @ActivistInvestor for your response. Actually, in my case, the second TValue element is a list of ObjectIDs, which makes things more complex.
The methods I posted work for any type of data, not only the types used in the example.
The problem is not that, but the way you are going about storing the data and identifying what each element represents. I don't think using delimited or bracketed lists is a wise choice for your use case. I would use multiple Xrecords instead, one for each list of ObjectIds that represent a value in the dictionary.
If the Value of each dictionary element is a list of objectids, you could store each as a separate xrecord, along with a 'master' xrecord containing the access keys, and the string key of the xrecord that holds the list of values.
This has the advantage of not having to load all of the data to access the elements of a particular key, and not having to write all the data when the elements for a particular key are modified. The DBDictionary keys for each of the xrecords that contain the values are only meaningful to the code that accesses them, so they can be GUID strings.
@youssefGC wrote:Thank you @ActivistInvestor for your response. Actually, in my case, the second TValue element is a list of ObjectIDs, which makes things more complex.
The methods I posted work for any type of data, not only the types used in the example.
The problem is not that, but the way you are going about storing the data and identifying what each element represents. I don't think using delimited or bracketed lists is a wise choice for your use case. I would use multiple Xrecords instead, one for each list of ObjectIds that represent a value in the dictionary.
If the Value of each dictionary element is a list of objectids, you could store each as a separate xrecord, along with a 'master' xrecord containing the access keys, and the string key of the xrecord that holds the list of values.
This has the advantage of not having to load all of the data to access the elements of a particular key, and not having to write all the data when the elements for a particular key are modified. The DBDictionary keys for each of the xrecords that contain the values are only meaningful to the code that accesses them, so they can be GUID strings.
What I said above is not entirely true. The code I posted will only store simple values in each dictionary entry's value.
I still believe that the correct way to address your problem is with multiple Xrecords (one representing each entry in a dictionary, with the first element being the 'key', and the remaining elements being the values).
However, if you insist on going about it the way you are, you should approach the solution to the problem in a more holistic way, to eliminate the confusion.
Here are altered versions of the previously-posted methods that accommodate conversion between Dictionary<TKey, List<TValue>> and ResultBuffer.
public static class Class1
{
public static ResultBuffer ToResultBuffer<TKey, TValue>(
this Dictionary<TKey, List<TValue>> items,
short keyCode,
short valueCode)
{
var result = new ResultBuffer();
foreach(var pair in items)
{
result.Add(new TypedValue(keyCode, pair.Key));
result.Add(new TypedValue((short)LispDataType.ListBegin));
foreach(TValue value in pair.Value)
result.Add(new TypedValue(valueCode, value));
result.Add(new TypedValue((short)LispDataType.ListEnd));
}
return result;
}
public static Dictionary<TKey, List<TValue>> ToDictionary<TKey, TValue>(
this ResultBuffer rb)
{
var result = new Dictionary<TKey, List<TValue>>();
var e = rb.GetEnumerator();
while(e.MoveNext())
{
var key = e.Current.Value;
if(!e.MoveNext())
throw new InvalidOperationException("Count mismatch");
if(e.Current.TypeCode != (short)LispDataType.ListBegin)
throw new InvalidOperationException("Invalid sequence, expecting ListBegin");
List<TValue> list = new List<TValue>();
while(e.MoveNext() && e.Current.TypeCode != (short)LispDataType.ListEnd)
{
list.Add((TValue)e.Current.Value);
}
if(e.Current.TypeCode != (short)LispDataType.ListEnd)
throw new InvalidOperationException("Malformed list, expecting ListEnd");
result.Add((TKey)key, list);
}
return result;
}
[CommandMethod("TEST")]
public static void Run()
{
/// Create and populate dictionary:
Dictionary<string, List<int>> map = new Dictionary<string, List<int>>();
int i = 1;
int v = 0;
foreach(string key in new[] { "First", "Second", "Third" })
{
List<int> list = new List<int>();
for(int j = 0; j < (3 * i); j++)
{
list.Add(v++);
}
map.Add(key, list);
++i;
}
/// Dump contents of dictionary:
DumpDictionary();
/// Convert dictionary to resultbuffer:
ResultBuffer resbuf = map.ToResultBuffer((short) DxfCode.Text, (short) DxfCode.Int32);
/// Dump contents of resultbuffer:
foreach(TypedValue tv in resbuf)
Write($"{(DxfCode)tv.TypeCode} = {tv.Value}");
/// Convert resultbuffer back to dictionary:
map = resbuf.ToDictionary<string, int>();
/// Dump contents of dictionary:
DumpDictionary();
void DumpDictionary()
{
foreach(var pair in map)
{
Write($"{pair.Key} = "
+ string.Join(", ",
pair.Value.Select(v => v.ToString())));
}
}
void Write(string fmt, params object[] args)
{
Application.DocumentManager.MdiActiveDocument?.
Editor.WriteMessage("\n" + fmt, args);
}
}
}
Command: TEST
First = 0, 1, 2
Second = 3, 4, 5, 6, 7, 8
Third = 9, 10, 11, 12, 13, 14, 15, 16, 17
Text = First
5016 = -1
Int32 = 0
Int32 = 1
Int32 = 2
5017 = -1
Text = Second
5016 = -1
Int32 = 3
Int32 = 4
Int32 = 5
Int32 = 6
Int32 = 7
Int32 = 8
5017 = -1
Text = Third
5016 = -1
Int32 = 9
Int32 = 10
Int32 = 11
Int32 = 12
Int32 = 13
Int32 = 14
Int32 = 15
Int32 = 16
Int32 = 17
5017 = -1
First = 0, 1, 2
Second = 3, 4, 5, 6, 7, 8
Third = 9, 10, 11, 12, 13, 14, 15, 16, 17
What I said above is not entirely true. The code I posted will only store simple values in each dictionary entry's value.
I still believe that the correct way to address your problem is with multiple Xrecords (one representing each entry in a dictionary, with the first element being the 'key', and the remaining elements being the values).
However, if you insist on going about it the way you are, you should approach the solution to the problem in a more holistic way, to eliminate the confusion.
Here are altered versions of the previously-posted methods that accommodate conversion between Dictionary<TKey, List<TValue>> and ResultBuffer.
public static class Class1
{
public static ResultBuffer ToResultBuffer<TKey, TValue>(
this Dictionary<TKey, List<TValue>> items,
short keyCode,
short valueCode)
{
var result = new ResultBuffer();
foreach(var pair in items)
{
result.Add(new TypedValue(keyCode, pair.Key));
result.Add(new TypedValue((short)LispDataType.ListBegin));
foreach(TValue value in pair.Value)
result.Add(new TypedValue(valueCode, value));
result.Add(new TypedValue((short)LispDataType.ListEnd));
}
return result;
}
public static Dictionary<TKey, List<TValue>> ToDictionary<TKey, TValue>(
this ResultBuffer rb)
{
var result = new Dictionary<TKey, List<TValue>>();
var e = rb.GetEnumerator();
while(e.MoveNext())
{
var key = e.Current.Value;
if(!e.MoveNext())
throw new InvalidOperationException("Count mismatch");
if(e.Current.TypeCode != (short)LispDataType.ListBegin)
throw new InvalidOperationException("Invalid sequence, expecting ListBegin");
List<TValue> list = new List<TValue>();
while(e.MoveNext() && e.Current.TypeCode != (short)LispDataType.ListEnd)
{
list.Add((TValue)e.Current.Value);
}
if(e.Current.TypeCode != (short)LispDataType.ListEnd)
throw new InvalidOperationException("Malformed list, expecting ListEnd");
result.Add((TKey)key, list);
}
return result;
}
[CommandMethod("TEST")]
public static void Run()
{
/// Create and populate dictionary:
Dictionary<string, List<int>> map = new Dictionary<string, List<int>>();
int i = 1;
int v = 0;
foreach(string key in new[] { "First", "Second", "Third" })
{
List<int> list = new List<int>();
for(int j = 0; j < (3 * i); j++)
{
list.Add(v++);
}
map.Add(key, list);
++i;
}
/// Dump contents of dictionary:
DumpDictionary();
/// Convert dictionary to resultbuffer:
ResultBuffer resbuf = map.ToResultBuffer((short) DxfCode.Text, (short) DxfCode.Int32);
/// Dump contents of resultbuffer:
foreach(TypedValue tv in resbuf)
Write($"{(DxfCode)tv.TypeCode} = {tv.Value}");
/// Convert resultbuffer back to dictionary:
map = resbuf.ToDictionary<string, int>();
/// Dump contents of dictionary:
DumpDictionary();
void DumpDictionary()
{
foreach(var pair in map)
{
Write($"{pair.Key} = "
+ string.Join(", ",
pair.Value.Select(v => v.ToString())));
}
}
void Write(string fmt, params object[] args)
{
Application.DocumentManager.MdiActiveDocument?.
Editor.WriteMessage("\n" + fmt, args);
}
}
}
Command: TEST
First = 0, 1, 2
Second = 3, 4, 5, 6, 7, 8
Third = 9, 10, 11, 12, 13, 14, 15, 16, 17
Text = First
5016 = -1
Int32 = 0
Int32 = 1
Int32 = 2
5017 = -1
Text = Second
5016 = -1
Int32 = 3
Int32 = 4
Int32 = 5
Int32 = 6
Int32 = 7
Int32 = 8
5017 = -1
Text = Third
5016 = -1
Int32 = 9
Int32 = 10
Int32 = 11
Int32 = 12
Int32 = 13
Int32 = 14
Int32 = 15
Int32 = 16
Int32 = 17
5017 = -1
First = 0, 1, 2
Second = 3, 4, 5, 6, 7, 8
Third = 9, 10, 11, 12, 13, 14, 15, 16, 17
Thank you for your code, it seems interesting. I will try to implement it in my case.
Yesterday, I tried to develop the following code:
public void StoreData(Document doc, Dictionary<ObjectId, List<ObjectId>> map)
{
string Masterkey = "key";
// Write object id s in NOD
Xrecord MasterxRec = new Xrecord();
using (Transaction tr = doc.Database.TransactionManager.StartTransaction())
{
try
{
// Find the NOD in the database
DBDictionary nod = (DBDictionary)tr.GetObject(doc.Database.NamedObjectsDictionaryId, OpenMode.ForWrite);
// Creates a new DBDictionary
if (!nod.Contains(Masterkey))
{
DBDictionary newDict = new DBDictionary();
nod.SetAt(Masterkey, newDict);
tr.AddNewlyCreatedDBObject(newDict, true);
}
MasterxRec = (Xrecord)tr.GetObject(nod.GetAt(Masterkey), OpenMode.ForWrite, false);
if (MasterxRec.ExtensionDictionary == null)
{
MasterxRec.CreateExtensionDictionary();
}
DBDictionary xDict = tr.GetObject(MasterxRec.ExtensionDictionary, OpenMode.ForWrite) as DBDictionary;
foreach (var item in map)
{
string key = item.Key.Handle.ToString();
List<ObjectId> lstChildren = item.Value as List<ObjectId>;
ResultBuffer rsb = new ResultBuffer(new TypedValue((int)DxfCode.SoftPointerId, item.Key));
Xrecord xr = new Xrecord();
foreach (var id in lstChildren)
{
rsb.Add(new TypedValue((int)DxfCode.SoftPointerId, id));
}
if (!xDict.Contains(key))
{
xr.Data = rsb;
xDict.SetAt(key, xr);
tr.AddNewlyCreatedDBObject(xr, true);
}
else
{
xr = (Xrecord)tr.GetObject(nod.GetAt(key), OpenMode.ForWrite, false);
xr.Data = rsb;
}
}
}
catch
{
doc.Editor.WriteMessage("\nError");
}
tr.Commit();
}
}
Thank you for your code, it seems interesting. I will try to implement it in my case.
Yesterday, I tried to develop the following code:
public void StoreData(Document doc, Dictionary<ObjectId, List<ObjectId>> map)
{
string Masterkey = "key";
// Write object id s in NOD
Xrecord MasterxRec = new Xrecord();
using (Transaction tr = doc.Database.TransactionManager.StartTransaction())
{
try
{
// Find the NOD in the database
DBDictionary nod = (DBDictionary)tr.GetObject(doc.Database.NamedObjectsDictionaryId, OpenMode.ForWrite);
// Creates a new DBDictionary
if (!nod.Contains(Masterkey))
{
DBDictionary newDict = new DBDictionary();
nod.SetAt(Masterkey, newDict);
tr.AddNewlyCreatedDBObject(newDict, true);
}
MasterxRec = (Xrecord)tr.GetObject(nod.GetAt(Masterkey), OpenMode.ForWrite, false);
if (MasterxRec.ExtensionDictionary == null)
{
MasterxRec.CreateExtensionDictionary();
}
DBDictionary xDict = tr.GetObject(MasterxRec.ExtensionDictionary, OpenMode.ForWrite) as DBDictionary;
foreach (var item in map)
{
string key = item.Key.Handle.ToString();
List<ObjectId> lstChildren = item.Value as List<ObjectId>;
ResultBuffer rsb = new ResultBuffer(new TypedValue((int)DxfCode.SoftPointerId, item.Key));
Xrecord xr = new Xrecord();
foreach (var id in lstChildren)
{
rsb.Add(new TypedValue((int)DxfCode.SoftPointerId, id));
}
if (!xDict.Contains(key))
{
xr.Data = rsb;
xDict.SetAt(key, xr);
tr.AddNewlyCreatedDBObject(xr, true);
}
else
{
xr = (Xrecord)tr.GetObject(nod.GetAt(key), OpenMode.ForWrite, false);
xr.Data = rsb;
}
}
}
catch
{
doc.Editor.WriteMessage("\nError");
}
tr.Commit();
}
}
There’s another way to do this without the need to use ResultBuffers
SetBinaryDataForKey, GetBinaryDataForKey or setBinaryData, getBinaryData in ARX respectively
SetBinaryDataForKey, will automatically create the extension dictionary and Xrecord.
All you need to do is implement is ISerializable on your data.
This is Python, but I hope it illustrates the concept
import traceback
from pyrx_imp import Ap, Db, Ed, Ge, Gi, Gs, Rx
# pickle == ISerializable
import pickle
#python dictionary
data = { "KEY1": 1, "KEY2": 2, "KEY3": 3}
def PyRxCmd_setit() -> None:
try:
esres = Ed.Editor.entSel("\nSelect: ")
ent = Db.Entity(esres[1], Db.OpenMode.kForWrite)
ent.setBinaryData("PYTHONTEST", pickle.dumps(data))
except Exception as err:
traceback.print_exception(err)
def PyRxCmd_getit() -> None:
try:
esres = Ed.Editor.entSel("\nSelect: ")
ent = Db.Entity(esres[1])
bOut = ent.getBinaryData("PYTHONTEST")
print(pickle.loads(bOut))
except Exception as err:
traceback.print_exception(err)
There’s another way to do this without the need to use ResultBuffers
SetBinaryDataForKey, GetBinaryDataForKey or setBinaryData, getBinaryData in ARX respectively
SetBinaryDataForKey, will automatically create the extension dictionary and Xrecord.
All you need to do is implement is ISerializable on your data.
This is Python, but I hope it illustrates the concept
import traceback
from pyrx_imp import Ap, Db, Ed, Ge, Gi, Gs, Rx
# pickle == ISerializable
import pickle
#python dictionary
data = { "KEY1": 1, "KEY2": 2, "KEY3": 3}
def PyRxCmd_setit() -> None:
try:
esres = Ed.Editor.entSel("\nSelect: ")
ent = Db.Entity(esres[1], Db.OpenMode.kForWrite)
ent.setBinaryData("PYTHONTEST", pickle.dumps(data))
except Exception as err:
traceback.print_exception(err)
def PyRxCmd_getit() -> None:
try:
esres = Ed.Editor.entSel("\nSelect: ")
ent = Db.Entity(esres[1])
bOut = ent.getBinaryData("PYTHONTEST")
print(pickle.loads(bOut))
except Exception as err:
traceback.print_exception(err)
Hi,
It looks like there's some confusion with extension dictionaries, 'named' dictionaries and xrecord.
In your code you check if the NOD contains a dictionary named MasterDict and create it if it does not already exists (so far so good).
Then you get an xrecord called MasterRec without checking if MasterDict contains it (which will not be the case if you have just created it). Your code will crash at line 25 if MasterDict does not contais it.
Then you get or create the MasterRec extension dictionary, why? What's the purpose of MasterRec Xrecord if you do not fill it with data? Why not directly fill MasterDict with the xrecords containg the data?
Hi,
It looks like there's some confusion with extension dictionaries, 'named' dictionaries and xrecord.
In your code you check if the NOD contains a dictionary named MasterDict and create it if it does not already exists (so far so good).
Then you get an xrecord called MasterRec without checking if MasterDict contains it (which will not be the case if you have just created it). Your code will crash at line 25 if MasterDict does not contais it.
Then you get or create the MasterRec extension dictionary, why? What's the purpose of MasterRec Xrecord if you do not fill it with data? Why not directly fill MasterDict with the xrecords containg the data?
How would one persist ObjectIds using Set/GetBinaryDataForKey() in a way that allows them to persist across editing sessions and be translated by deep-clone/wblock-clone operations?
@daniel_cadext wrote:There’s another way to do this without the need to use ResultBuffers
SetBinaryDataForKey, GetBinaryDataForKey or setBinaryData, getBinaryData in ARX respectively
How would one persist ObjectIds using Set/GetBinaryDataForKey() in a way that allows them to persist across editing sessions and be translated by deep-clone/wblock-clone operations?
@daniel_cadext wrote:There’s another way to do this without the need to use ResultBuffers
SetBinaryDataForKey, GetBinaryDataForKey or setBinaryData, getBinaryData in ARX respectively
Good point! might not be the best tool for this.
Good point! might not be the best tool for this.
Indeed what you say is true: the MasterDic does not contain the MasterRec. Before creating and storing it, I must ensure this.
To my understanding, the storage architecture generally involves creating an "ExtensionDictionary". On this, I create keys to which I attach each XRecord(). XRecords contain information in the form of a series of TypedValues.
I attempted to adapt this architecture to my case: creating a main dictionary "Master" where I want to store XRecords for each main entity "Parent" and the secondary entities "Children" associated with it.
In my case, "MasterxRec" serves as the "Parent", but after your correction, it seems unnecessary. Thank you @_gile again for your feedback.
Indeed what you say is true: the MasterDic does not contain the MasterRec. Before creating and storing it, I must ensure this.
To my understanding, the storage architecture generally involves creating an "ExtensionDictionary". On this, I create keys to which I attach each XRecord(). XRecords contain information in the form of a series of TypedValues.
I attempted to adapt this architecture to my case: creating a main dictionary "Master" where I want to store XRecords for each main entity "Parent" and the secondary entities "Children" associated with it.
In my case, "MasterxRec" serves as the "Parent", but after your correction, it seems unnecessary. Thank you @_gile again for your feedback.
@youssefGC a écrit :
To my understanding, the storage architecture generally involves creating an "ExtensionDictionary".
No, Xrecords can be conained either by enamed dictionaries or by extension dictionaries. Both are the same type: DBDictionary, only the access changes, the first ones are got with their name (key), the second ones by the DBObject they belongs to..
@youssefGC a écrit :
To my understanding, the storage architecture generally involves creating an "ExtensionDictionary".
No, Xrecords can be conained either by enamed dictionaries or by extension dictionaries. Both are the same type: DBDictionary, only the access changes, the first ones are got with their name (key), the second ones by the DBObject they belongs to..
I tried to adapt your code to my case, but I encountered an exception error. I'm unable to add the data from the result buffer to the NOD of the document.
// Add Data
Dictionary<ObjectId, List<ObjectId>> map = new Dictionary<ObjectId, List<ObjectId>>();
List<ObjectId> templst = new List<ObjectId>();
templst.Add(ent2.ObjectId);
templst.Add(ent3.ObjectId);
map.Add(ent1.ObjectId, templst);
templst = new List<ObjectId>();
templst.Add(ent5.ObjectId);
map.Add(ent4.ObjectId, templst);
// Convert dictionary to resultbuffer
ResultBuffer resbuf = map.ToResultBuffer((short)DxfCode.SoftPointerId, (short)DxfCode.SoftPointerId);
ReadAndWriteUsingNOD _instanceClass = new ReadAndWriteUsingNOD();
_instanceClass.updateNOD(doc, "Test", resbuf);
Here is the method "updateNOD" which I used to write to the NOD:
public void updateNOD(Document doc, string key, ResultBuffer rsbf)
{
Database db = doc.Database;
using (Transaction tr = db.TransactionManager.StartTransaction())
{
Xrecord xRec = new Xrecord();
try
{
// Find the NOD in the database
DBDictionary nod = (DBDictionary)tr.GetObject(db.NamedObjectsDictionaryId, OpenMode.ForWrite);
// We use Xrecord class to store data in Dictionaries
if (nod.Contains(key)) // existing key
{
xRec = (Xrecord)tr.GetObject(nod.GetAt(key), OpenMode.ForWrite, false);
xRec.Data = rsbf;
}
else // new element
{
xRec.Data = rsbf;
nod.SetAt(key, xRec);
tr.AddNewlyCreatedDBObject(xRec, true);
}
tr.Commit();
}
catch
{
doc.Editor.WriteMessage("\nError");
}
}
}
I found an error on line 21
I tried to adapt your code to my case, but I encountered an exception error. I'm unable to add the data from the result buffer to the NOD of the document.
// Add Data
Dictionary<ObjectId, List<ObjectId>> map = new Dictionary<ObjectId, List<ObjectId>>();
List<ObjectId> templst = new List<ObjectId>();
templst.Add(ent2.ObjectId);
templst.Add(ent3.ObjectId);
map.Add(ent1.ObjectId, templst);
templst = new List<ObjectId>();
templst.Add(ent5.ObjectId);
map.Add(ent4.ObjectId, templst);
// Convert dictionary to resultbuffer
ResultBuffer resbuf = map.ToResultBuffer((short)DxfCode.SoftPointerId, (short)DxfCode.SoftPointerId);
ReadAndWriteUsingNOD _instanceClass = new ReadAndWriteUsingNOD();
_instanceClass.updateNOD(doc, "Test", resbuf);
Here is the method "updateNOD" which I used to write to the NOD:
public void updateNOD(Document doc, string key, ResultBuffer rsbf)
{
Database db = doc.Database;
using (Transaction tr = db.TransactionManager.StartTransaction())
{
Xrecord xRec = new Xrecord();
try
{
// Find the NOD in the database
DBDictionary nod = (DBDictionary)tr.GetObject(db.NamedObjectsDictionaryId, OpenMode.ForWrite);
// We use Xrecord class to store data in Dictionaries
if (nod.Contains(key)) // existing key
{
xRec = (Xrecord)tr.GetObject(nod.GetAt(key), OpenMode.ForWrite, false);
xRec.Data = rsbf;
}
else // new element
{
xRec.Data = rsbf;
nod.SetAt(key, xRec);
tr.AddNewlyCreatedDBObject(xRec, true);
}
tr.Commit();
}
catch
{
doc.Editor.WriteMessage("\nError");
}
}
}
I found an error on line 21
Well, I obviously didn't test writing/reading to/from an XRecord, and the problem is that it doesn't like the LispDataType.ListBegin/end codes. That's because it was based on existing code that was designed to pass List and Dictionary objects between LISP and managed code.
This corrects the problem, and was round-trip tested:
public static class ResultBufferUtils
{
/// ResultBuffer conversion to/from Dictionary<TKey, List<TValue>>:
public static ResultBuffer ToResultBuffer<TKey, TValue>(
this Dictionary<TKey, IEnumerable<TValue>> items,
DxfCode keyCode,
DxfCode valueCode)
{
return ToResultBuffer<TKey, TValue>(items, (short) keyCode, (short) valueCode);
}
public static ResultBuffer ToResultBuffer<TKey, TValue>(
this Dictionary<TKey, IEnumerable<TValue>> items,
short keyCode,
short valueCode)
{
if(items == null)
throw new ArgumentNullException(nameof(items));
var result = new ResultBuffer();
foreach(var pair in items)
{
result.Add(new TypedValue(keyCode, pair.Key));
result.Add(listBegin);
if(pair.Value != null)
{
foreach(TValue value in pair.Value)
result.Add(new TypedValue(valueCode, value));
}
result.Add(listEnd);
}
return result;
}
/// Note: In AcMgdLib, the name of this method was
/// changed to 'ToListDictionary()', to accomodate both
/// dictionaries with simple values and dictionaries
/// with List<T> values.
public static Dictionary<TKey, IEnumerable<TValue>>
ToDictionary<TKey, TValue>(this ResultBuffer resbuf)
{
if(resbuf == null)
throw new ArgumentNullException(nameof(resbuf));
var result = new Dictionary<TKey, IEnumerable<TValue>>();
var e = resbuf.GetEnumerator();
while(e.MoveNext())
{
var key = e.Current.Value;
if(!e.MoveNext())
throw new InvalidOperationException("Count mismatch");
TypedValue cur = e.Current;
if(!IsListBegin(cur))
throw new InvalidOperationException(
$"Malformed list: expecting List Begin: {cur.TypeCode}, {cur.Value}");
ICollection<TValue> list = new List<TValue>();
while(true)
{
if(!e.MoveNext())
throw new InvalidOperationException(
$"Malformed list: expecting List End: {cur.TypeCode}, {cur.Value}");
cur = e.Current;
if(IsListEnd(cur))
break;
list.Add((TValue) cur.Value);
}
result.Add((TKey)key, list);
}
return result;
}
static readonly TypedValue listBegin = new TypedValue(102, "{");
static readonly TypedValue listEnd = new TypedValue(102, "}");
// Note: Need to avoid using TypedValue.Equals() because of a bug in that API.
static bool IsListBegin(TypedValue tv)
=> tv.TypeCode == 102 && object.Equals(tv.Value, "{");
static bool IsListEnd(TypedValue tv)
=> tv.TypeCode == 102 && object.Equals(tv.Value, "}");
}
I also added these APIs to this library, along with a few example commands that were used to test it.
Well, I obviously didn't test writing/reading to/from an XRecord, and the problem is that it doesn't like the LispDataType.ListBegin/end codes. That's because it was based on existing code that was designed to pass List and Dictionary objects between LISP and managed code.
This corrects the problem, and was round-trip tested:
public static class ResultBufferUtils
{
/// ResultBuffer conversion to/from Dictionary<TKey, List<TValue>>:
public static ResultBuffer ToResultBuffer<TKey, TValue>(
this Dictionary<TKey, IEnumerable<TValue>> items,
DxfCode keyCode,
DxfCode valueCode)
{
return ToResultBuffer<TKey, TValue>(items, (short) keyCode, (short) valueCode);
}
public static ResultBuffer ToResultBuffer<TKey, TValue>(
this Dictionary<TKey, IEnumerable<TValue>> items,
short keyCode,
short valueCode)
{
if(items == null)
throw new ArgumentNullException(nameof(items));
var result = new ResultBuffer();
foreach(var pair in items)
{
result.Add(new TypedValue(keyCode, pair.Key));
result.Add(listBegin);
if(pair.Value != null)
{
foreach(TValue value in pair.Value)
result.Add(new TypedValue(valueCode, value));
}
result.Add(listEnd);
}
return result;
}
/// Note: In AcMgdLib, the name of this method was
/// changed to 'ToListDictionary()', to accomodate both
/// dictionaries with simple values and dictionaries
/// with List<T> values.
public static Dictionary<TKey, IEnumerable<TValue>>
ToDictionary<TKey, TValue>(this ResultBuffer resbuf)
{
if(resbuf == null)
throw new ArgumentNullException(nameof(resbuf));
var result = new Dictionary<TKey, IEnumerable<TValue>>();
var e = resbuf.GetEnumerator();
while(e.MoveNext())
{
var key = e.Current.Value;
if(!e.MoveNext())
throw new InvalidOperationException("Count mismatch");
TypedValue cur = e.Current;
if(!IsListBegin(cur))
throw new InvalidOperationException(
$"Malformed list: expecting List Begin: {cur.TypeCode}, {cur.Value}");
ICollection<TValue> list = new List<TValue>();
while(true)
{
if(!e.MoveNext())
throw new InvalidOperationException(
$"Malformed list: expecting List End: {cur.TypeCode}, {cur.Value}");
cur = e.Current;
if(IsListEnd(cur))
break;
list.Add((TValue) cur.Value);
}
result.Add((TKey)key, list);
}
return result;
}
static readonly TypedValue listBegin = new TypedValue(102, "{");
static readonly TypedValue listEnd = new TypedValue(102, "}");
// Note: Need to avoid using TypedValue.Equals() because of a bug in that API.
static bool IsListBegin(TypedValue tv)
=> tv.TypeCode == 102 && object.Equals(tv.Value, "{");
static bool IsListEnd(TypedValue tv)
=> tv.TypeCode == 102 && object.Equals(tv.Value, "}");
}
I also added these APIs to this library, along with a few example commands that were used to test it.
As the rule of thumb regarding to using NamedDictionary of the drawing database, you DO NOT add your data (Xrecords) as DIRECT CHILDREN of the database's NamedDictionary. The direct children of the database's dictionary should always be DBDictionaries, which are specific for different AutoCAD vertical products/plugins. So, you need to first add your own DBDictionary to the NamedDictionary of the database, then add your data as Xrecord, or add another level of DBDictionary...
Norman Yuan
As the rule of thumb regarding to using NamedDictionary of the drawing database, you DO NOT add your data (Xrecords) as DIRECT CHILDREN of the database's NamedDictionary. The direct children of the database's dictionary should always be DBDictionaries, which are specific for different AutoCAD vertical products/plugins. So, you need to first add your own DBDictionary to the NamedDictionary of the database, then add your data as Xrecord, or add another level of DBDictionary...
Norman Yuan
There isn't any problem using an Xrecord as a direct child of the NamedObjectsDictionary, so long as you use a Key that is reasonably-distinct and would not likely be used by another app or AutoCAD. Having to use a child DBDictionary is a pita, and doesn't serve any useful purpose other than to complicate code that must access the Xrecord. Using a child DBDictionary is something you should do if you have multiple, related dictionary entries that need to be stored in the NOD (e.g., you may need to deep clone the parent dictionary and all of its contents to another database, which is easier when they are all contained in a single parent dictionary). However, in the case where you only need to store a single Xrecord, and you don't expect that to change, there is no problem with storing it in the root dictionary.
The fact is, that any two applications could potentially use dictionary keys that collide, regardless of whether they are keys for a child DBDictionary or an Xrecord.
The only rule of thumb is to avoid the use of any key that starts with "ACAD".
There isn't any problem using an Xrecord as a direct child of the NamedObjectsDictionary, so long as you use a Key that is reasonably-distinct and would not likely be used by another app or AutoCAD. Having to use a child DBDictionary is a pita, and doesn't serve any useful purpose other than to complicate code that must access the Xrecord. Using a child DBDictionary is something you should do if you have multiple, related dictionary entries that need to be stored in the NOD (e.g., you may need to deep clone the parent dictionary and all of its contents to another database, which is easier when they are all contained in a single parent dictionary). However, in the case where you only need to store a single Xrecord, and you don't expect that to change, there is no problem with storing it in the root dictionary.
The fact is, that any two applications could potentially use dictionary keys that collide, regardless of whether they are keys for a child DBDictionary or an Xrecord.
The only rule of thumb is to avoid the use of any key that starts with "ACAD".
If the number of "children" is not large, and the logic is saved in the file, it is advisable to also check XDATA with a list of DxfCode.ExtendedDataHandle
It's true that it came out a bad name for XDATA but because of incorrect use of the RegAppTableRecord registration
var values = new _AcDb.TypedValue[12 + InnerPolygonsHandles.Count];
values[0] = new _AcDb.TypedValue( /*1001*/ (short)_AcDb.DxfCode.ExtendedDataRegAppName, MyXData.MidotPolygonAppId );
values[1] = new _AcDb.TypedValue( /*1071*/ (short)_AcDb.DxfCode.ExtendedDataInteger32, LocalityId );
values[2] = new _AcDb.TypedValue( /*1071*/ (short)_AcDb.DxfCode.ExtendedDataInteger32, MapId );
values[3] = new _AcDb.TypedValue( /*1071*/ (short)_AcDb.DxfCode.ExtendedDataInteger32, DrawingId );
values[4] = new _AcDb.TypedValue( /*1071*/ (short)_AcDb.DxfCode.ExtendedDataInteger32, PolyId );
values[5] = new _AcDb.TypedValue( /*1071*/ (short)_AcDb.DxfCode.ExtendedDataInteger32, BuildingId );
values[6] = new _AcDb.TypedValue( /*1071*/ (short)_AcDb.DxfCode.ExtendedDataInteger32, PolyNum );
values[7] = new _AcDb.TypedValue( /*1071*/ (short)_AcDb.DxfCode.ExtendedDataInteger32, Floor );
values[8] = new _AcDb.TypedValue( /*1071*/ (short)_AcDb.DxfCode.ExtendedDataInteger32, MainKind );
values[9] = new _AcDb.TypedValue( /*1071*/ (short)_AcDb.DxfCode.ExtendedDataInteger32, SubKind );
values[10] = new _AcDb.TypedValue( /*1005*/ (short)_AcDb.DxfCode.ExtendedDataHandle, AttributesBlockHandle );
values[11] = new _AcDb.TypedValue( /*1071*/ (short)_AcDb.DxfCode.ExtendedDataInteger32, InnerPolygonsHandles.Count );
for( int i = 0; i < InnerPolygonsHandles.Count; i++ ) {
values[12 + i] = new _AcDb.TypedValue( /*1005*/ (short)_AcDb.DxfCode.ExtendedDataHandle, InnerPolygonsHandles[i] );
}
If the number of "children" is not large, and the logic is saved in the file, it is advisable to also check XDATA with a list of DxfCode.ExtendedDataHandle
It's true that it came out a bad name for XDATA but because of incorrect use of the RegAppTableRecord registration
var values = new _AcDb.TypedValue[12 + InnerPolygonsHandles.Count];
values[0] = new _AcDb.TypedValue( /*1001*/ (short)_AcDb.DxfCode.ExtendedDataRegAppName, MyXData.MidotPolygonAppId );
values[1] = new _AcDb.TypedValue( /*1071*/ (short)_AcDb.DxfCode.ExtendedDataInteger32, LocalityId );
values[2] = new _AcDb.TypedValue( /*1071*/ (short)_AcDb.DxfCode.ExtendedDataInteger32, MapId );
values[3] = new _AcDb.TypedValue( /*1071*/ (short)_AcDb.DxfCode.ExtendedDataInteger32, DrawingId );
values[4] = new _AcDb.TypedValue( /*1071*/ (short)_AcDb.DxfCode.ExtendedDataInteger32, PolyId );
values[5] = new _AcDb.TypedValue( /*1071*/ (short)_AcDb.DxfCode.ExtendedDataInteger32, BuildingId );
values[6] = new _AcDb.TypedValue( /*1071*/ (short)_AcDb.DxfCode.ExtendedDataInteger32, PolyNum );
values[7] = new _AcDb.TypedValue( /*1071*/ (short)_AcDb.DxfCode.ExtendedDataInteger32, Floor );
values[8] = new _AcDb.TypedValue( /*1071*/ (short)_AcDb.DxfCode.ExtendedDataInteger32, MainKind );
values[9] = new _AcDb.TypedValue( /*1071*/ (short)_AcDb.DxfCode.ExtendedDataInteger32, SubKind );
values[10] = new _AcDb.TypedValue( /*1005*/ (short)_AcDb.DxfCode.ExtendedDataHandle, AttributesBlockHandle );
values[11] = new _AcDb.TypedValue( /*1071*/ (short)_AcDb.DxfCode.ExtendedDataInteger32, InnerPolygonsHandles.Count );
for( int i = 0; i < InnerPolygonsHandles.Count; i++ ) {
values[12 + i] = new _AcDb.TypedValue( /*1005*/ (short)_AcDb.DxfCode.ExtendedDataHandle, InnerPolygonsHandles[i] );
}
Can't find what you're looking for? Ask the community or share your knowledge.