The Revit API 2012 provides an official way to extend the Element data. In the old days, we could only create some invisible parameters and attach them to the elements where we want to store some extra data. Now the Extensible Storage API can directly address such very basic need and is more powerful.
Its power lies not only at that the Extensible Storage API can store various data types from the simplest such as integer and bool to complex such as Array and Map or even nested Entity, but also at the unnecessary complexities of and many impositions by the API.
We are going to store some data of all allowed .NET native types by the Extensible Storage API and read them back respectively in two separate commands to demonstrate these in more detail. Here is the code to store various .NET kinds of data to a selected Element:
SchemaBuilder sb = new SchemaBuilder(new Guid("DA4AAE5A-4EE1-45A8-B3E8-F790C84CC44F"));
sb.SetSchemaName("SchemaTest1");
FieldBuilder fb1 = sb.AddSimpleField("FieldTest1", typeof(int));
FieldBuilder fb2 = sb.AddSimpleField("FieldTest2", typeof(double));
fb2.SetUnitType(UnitType.UT_Area);
FieldBuilder fb3 = sb.AddSimpleField("FieldTest3", typeof(bool));
FieldBuilder fb4 = sb.AddSimpleField("FieldTest4", typeof(string));
FieldBuilder fb5 = sb.AddSimpleField("FieldTest5", typeof(Guid));
FieldBuilder fb6 = sb.AddArrayField("FieldTest6", typeof(int));
FieldBuilder fb7 = sb.AddMapField("FieldTest7", typeof(int), typeof(string));
Schema schema = sb.Finish();
Entity ent = new Entity(schema);
ent.Set<int>("FieldTest1", 123);
ent.Set<double>("FieldTest2", 123.456, DisplayUnitType.DUT_SQUARE_INCHES);
ent.Set<bool>("FieldTest3", false);
ent.Set<string>("FieldTest4", "hi");
ent.Set<Guid>("FieldTest5", Guid.NewGuid());
IList<int> list = new List<int>() {111, 222, 333 };
ent.Set<IList<int>>("FieldTest6", list);
IDictionary<int, string> dict = new Dictionary<int, string>()
{
{111, "aaa"},
{222, "bbb"},
{333, "ccc"}
};
ent.Set<IDictionary<int, string>>("FieldTest7", dict);
CachedDoc.GetElement(picked).SetEntity(ent);
In terms of how to pick the element, wrap all the code into a Transaction, define an External Command, and even trigger the command from a Ribbon button, please refer to previous posts for ideas and code examples. We will not duplicate things here for brevity purpose.
Many none-ExtensibleStorageAPI relevant stuffs have been omitted, but some points are still worth of mentioning.
• The GUID string had better be explicitly provided and kept recorded somewhere since it is also needed when we read the same data out.
• Setting the name for the Schema is required. Otherwise, the exception ‘Autodesk.Revit.Exceptions.InvalidOperationException: SchemaName is not set.’ would come up when the ExtensibleStorage.SchemaBuilder.Finish() method is called. The fact is that the name does not really serve as the identifier for the schema nor is it needed when we read the same schema out later.
• The double type data requires a UnitType to be specified for the just created FieldBuilder. Otherwise, the exception saying ‘Autodesk.Revit.Exceptions.InvalidOperationException: Units are required for field FieldTest2’ would appear.
• The above fact indicates that even for a unit-less double data like ratio, we still have to provide a UnitType for it, though the UNIT can only be meaningless for sure.
• The data of Integer, Bool, String or Guid type does not have to be provided with the unit information, and it seems natural, good and less trouble.
• Thoughtful readers may wonder whether the data of an Array of double type has the same UnitType imposition. Not so sure, please feel free to give it a try if interested.
• The AddArrayField() method has to be used to create an Array or List type field. If something like sb.AddSimpleField("FieldTest6", typeof(List<int>)) or sb.AddSimpleField("FieldTest6", typeof(IList<int>)) is used, the exception of ‘Autodesk.Revit.Exceptions.ArgumentException: Input type is not a recognized Revit API type’ would just show up.
• The AddMapField () method has to be used to create a Map or Dictionary type field. Otherwise, a similar exception as the above would also be thrown out.
• When the Entity.Set method is used to set a double value the signature that has the DisplayUnitType argument has to be applied. Otherwise, the exception of ‘Autodesk.Revit.Exceptions.ArgumentException: The displayUnits value is not compatible with the field description.’ would be thrown out for a usage like ent.Set<double>("FieldTest2", 123.456). The message is a bit misleading since no DisplayUnitType is specified at all in the call. It might mean that the display unit type is required for any double type data regardless of whether they are unit-less.
• Nothing like SetArrayField or SetMapField as those field addition ones is provided in the Entity class to address setting complicated data fields differently from those simple fields. This time, the same signature Entity.Set<T>(…) handles them all.
• However, the IList has to be used for Array and IDictionary for Map in the Entity.Set method. Otherwise, another exception is waiting there for us.
Oh, there are already so many points for us to keep in mind for the above simple usages. Do not be surprised if more are found. Now let us look at how to read the same kinds of data out from the same Fields, Entity, Schema and in turn the Element.
Schema sch = Schema.Lookup(new Guid("DA4AAE5A-4EE1-45A8-B3E8-F790C84CC44F"));
Entity ent = CachedDoc.GetElement(picked).GetEntity(sch);
string info = "Fields in the Schema:\n";
info += string.Format("{0} : {1}\n", "FieldTest1", ent.Get<int>("FieldTest1").ToString());
info += string.Format("{0} : {1}\n", "FieldTest2", ent.Get<double>("FieldTest2", DisplayUnitType.DUT_SQUARE_INCHES).ToString());
info += string.Format("{0} : {1}\n", "FieldTest3", ent.Get<bool>("FieldTest3").ToString());
info += string.Format("{0} : {1}\n", "FieldTest4", ent.Get<string>("FieldTest4").ToString());
info += string.Format("{0} : {1}\n", "FieldTest5", ent.Get<Guid>("FieldTest5").ToString());
info += "FieldTest6:\n";
IList<int> list = ent.Get<IList<int>>("FieldTest6");
int index = 0;
foreach (int e in list)
{
info += string.Format("\t{0} : {1}\n", ++index, e);
}
info += "FieldTest7:\n";
IDictionary<int, string> dict = ent.Get<IDictionary<int, string>>("FieldTest7");
foreach (KeyValuePair<int, string> e in dict)
{
info += string.Format("\t{0} : {1}\n", e.Key, e.Value);
}
MessageBox.Show(info);
Let us also list out some highlights here about the short data retrieving code:
• Use the Schema.Lookup() method and provide it the same GUID string so that the same Schema can be got back. In this case, we can forget about the Schema name.
• If we built up the Schema the same way as in the first command instead of looking it up, an exception saying ‘Autodesk.Revit.Exceptions.InvalidOperationException: A different Schema with the same identity already exists.’ would be thrown out.
• Now the GetEntity() of the same Element needs to be called to retrieve back the same Entity.
• Similar situation happens again that the Entity.Get<double>(string) signature does not work at all. If it were used, the exception ‘Autodesk.Revit.Exceptions.ArgumentException: The displayUnits value is not compatible with the field description. ’ would be thrown out.
• Instead, the signature that has the DisplayUnitType argument must be used for reading any data with the DOUBLE type. Readers may wonder why the signature Entity.Get<double>(string) is there then, just to confuse people or give them a chance to read the exception message maybe?
• What will happen if the given DisplayUnitType is different from the one as specified when the Field being defined? Likely, another exception would come up. Once again, it just means trouble for unit-less double values like ratio.
• Once again, the read-back Array data has to be of type IList<T> and Map of type IDictionary<T>. Readers may wonder what benefits it offers more that the type has to be explicitly given in the Entity.Get call than being casted back afterwards.
If the above code is put into another External Command, when it runs and the same Window is picked, all the various values will be properly reported back in a message box:
The Revit Addin Wizard (RevitAddinWizard) is going to provide a coder to help generate Extensible Storage code.
Recent Comments