The Revit API 2012 provides an official way to extend the Element data now. In the old days, we could only create some cumbersome invisible parameters and attach them to the elements where we want to store some extra data. Now the Extensible Storage API can directly address so basic a need for us and has much power.
However, as introduced and demonstrated before, unnecessary complexities, impositions, redundancies and inconsistencies are here and there. In case any of those points as mentioned repeatedly in early posts slips out of our minds, the data storage or retrieval would just fail. In addition, the out-of-box supported data types are just a few, far less than enough.
Things become super easy and cool with the Revit Element Data Extension especially the version 2. In this article, let us see how to use the Revit Element Data Extension 2 to sore some very complex data including custom class instances, ArrayList, Revit UV, XYZ and ElementId, and even Dictionary to an Element and retrieve the same data back from the same Element extremely convenient.
Here is the Revit Element Data Extension 2:
using System;
using System.Xml;
using System.Runtime.Serialization;
using System.IO;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.ExtensibleStorage; //Revit API 2012 only
namespace RevitAddinWizard
{
public static class ElementDataExtension2
{
private const string SingleStringFiledName = "SingleStringForAll";
public static void SetData(this Element e, string id, object o)
{
SchemaBuilder sb = new SchemaBuilder(new Guid(id));
sb.SetSchemaName(id.Replace("-", ""));
FieldBuilder fb = sb.AddSimpleField(SingleStringFiledName, typeof(string));
DataContractSerializer serializer = new DataContractSerializer(o.GetType());
using (MemoryStream ms = new MemoryStream())
{
serializer.WriteObject(ms, o);
ms.Seek(0, SeekOrigin.Begin);
using (StreamReader reader = new StreamReader(ms))
{
Entity ent = new Entity(sb.Finish());
ent.Set<string>(SingleStringFiledName, reader.ReadToEnd());
e.SetEntity(ent);
}
}
}
public static T GetData<T>(this Element e, string id)
{
Schema sch = Schema.Lookup(new Guid(id));
string s = e.GetEntity(sch).Get<string>(SingleStringFiledName);
DataContractSerializer serializer = new DataContractSerializer(typeof(T));
using (XmlReader reader = XmlReader.Create(new StringReader(s)))
{
return (T)serializer.ReadObject(reader);
}
}
}
}
Here is some test code to write a Dictionary data to a selected Element using the Revit Element Data Extension 2:
Reference picked = CachedUiApp.ActiveUIDocument.Selection.PickObject(ObjectType.Element, "Pick a window to store out data to");
Reference picked1 = CachedUiApp.ActiveUIDocument.Selection.PickObject(ObjectType.Element, "Pick an element to store its ElementId into the window");
using (Transaction trans = new Transaction(CachedDoc, "AttachDataTo"))
{
trans.Start();
ComplexDataSample complexData = new ComplexDataSample();
complexData.data = new MyData
{
byteVar = 255,
charVar = 'a',
ushortVar = 65535,
longVar = 1234567890,
decimalVar = 1.123456789123456789M,
dateTimeVar = new DateTime(2012, 2, 14, 23, 59, 59)
};
complexData.data1 = new MyData1
{
doubleArray = new double[] { 1.11, 2.22, 3.33 }
};
complexData.data2 = new MyData2
{
strList = new List<string> { "User1", "User2", "User3" }
};
complexData.arrayList = new ArrayList
{
true,
(char)'a',
(byte)111,
(short)22222,
(int)33333333,
(long)44444444444444444,
(float)5.5555e35,
(double)6.6666e166,
(decimal)7.7777777777777777777777777M,
new DateTime(8888, 8, 8, 8, 8, 8),
"Hi guys!"
};
complexData.color = new Autodesk.Revit.DB.Color(11, 22, 33);
complexData.uvWrapper = new UVWrapper(new UV(1.1, 2.2));
complexData.xyzWrapper = new XYZWrapper(new XYZ(1.1, 2.2, 3.3));
complexData.elementId = CachedDoc.GetElement(picked1).Id.IntegerValue;
complexData.str2IntMap = new Dictionary<string, int>()
{
{ "AAA", 111},
{ "BBB", 222},
{ "CCC", 333},
};
CachedDoc.GetElement(picked).SetData2("DA4AAE5A-4EE1-45A8-B3E8-F790C84CC44F", complexData);
trans.Commit();
}
Here is the code to read back the same Dictionary data from the same Element:
Reference picked = CachedUiApp.ActiveUIDocument.Selection.PickObject(ObjectType.Element, "Pick the window having our data");
using (Transaction trans = new Transaction(CachedDoc, "ReadDataFrom"))
{
trans.Start();
ComplexDataSample complexData = CachedDoc.GetElement(picked).GetData2<ComplexDataSample>("DA4AAE5A-4EE1-45A8-B3E8-F790C84CC44F");
string str = string.Empty;
FieldInfo[] propInfoArrary = typeof(MyData).GetFields();
str += "MyData:\n";
foreach (FieldInfo fi in propInfoArrary)
{
object obj = typeof(MyData).InvokeMember(fi.Name, BindingFlags.GetField, null, complexData.data, null);
str += string.Format("{0} : {1}\n", fi.Name, obj == null ? string.Empty : obj.ToString());
}
str += "MyData1:\n";
for (int i = 0; i < complexData.data1.doubleArray.Length; i++)
{
str += string.Format("#{0} : {1}\n", i, complexData.data1.doubleArray[i]);
}
str += "MyData2:\n";
for (int i = 0; i < complexData.data2.strList.Count; i++)
{
str += string.Format("#{0} : {1}\n", i + 1, complexData.data2.strList[i]);
}
str += "ArrayList:\n";
for (int i = 0; i < complexData.arrayList.Count; i++)
{
str += complexData.arrayList[i].ToString() + "\n";
}
Autodesk.Revit.DB.Color color = complexData.color;
str += string.Format("Revit Color: R-{0} G-{1} B-{2}\n", color.Red, color.Green, color.Blue);
UV uv = complexData.uvWrapper.GetUV();
str += string.Format("U: {0} V: {1}\n", uv.U, uv.V);
XYZ xyz = complexData.xyzWrapper.GetXYZ();
str += string.Format("X: {0} Y: {1} Z: {2}\n", xyz.X, xyz.Y, xyz.Z);
str += string.Format("ElementId: {0}\n", complexData.elementId);
str += "Dictionary:\n";
int index = 0;
foreach (KeyValuePair<string, int> entry in complexData.str2IntMap)
{
str += string.Format("Entry #{0}: {1}/{2}\n", ++index, entry.Key, entry.Value);
}
MessageBox.Show(str, "MyData");
ElementId id = new ElementId(complexData.elementId);
if (id == ElementId.InvalidElementId) // To verify the element id
MessageBox.Show("The read-back id is wrong!");
else
CachedUiApp.ActiveUIDocument.ShowElements(id);
trans.Commit();
}
If the above test code is put into an external command, we can happily verify that everything works just fine.
Here are the custom data classes that are referenced by the data variable.
public class MyData
{
public byte byteVar;
public ushort ushortVar;
public long longVar;
public char charVar;
public decimal decimalVar;
public DateTime dateTimeVar;
}
public class MyData1
{
public double[] doubleArray;
}
public class MyData2
{
public List<string> strList;
}
public class ComplexDataSample
{
public MyData data;
public MyData1 data1;
public MyData2 data2;
public ArrayList arrayList;
public Autodesk.Revit.DB.Color color;
public UVWrapper uvWrapper;
public XYZWrapper xyzWrapper;
public int elementId;
public Dictionary<string, int> str2IntMap;
}
As can be seen, those complexities and impositions all go away. We do not have to specify the same schema GUIDs, scheme names, field names, data types, or unit types repeatedly. We do not have to care about all those intermediate objects such as Schema, SchemaBuilder, Field, FieldBuilder, or Entity anymore either.
What we need to care about now are what we really want, the Element itself, the data object no matter how complex it is, and the Schema identifier. If they are consistent with each other, it will be very hard to make the data storage or retrieval fail.
Life is really cool and easy now, isn’t it?
The Revit Addin Wizard (RevitAddinWizard) is going to provide a coder to help generate Extensible Storage code.
Recent Comments