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.
Its power lies not only at that the Extensible Storage API can store various types of data ranging from the simplest such as integer and bool to quite complex such as Array and Map or even nested Extensible Storage Entity (Sub Schema), but also at the unnecessary complexities of and many impositions by the API itself.
In this article, let us see how to store a couple of sub schemas (nested Entity type data) into an Element.
Reference picked = CachedUiApp.ActiveUIDocument.Selection.PickObject(ObjectType.Element, "Pick a window");
using (Transaction trans = new Transaction(CachedDoc, "AttachDataTo"))
{
trans.Start();
SchemaBuilder sb1 = new SchemaBuilder(new Guid("1A4AAE5A-4EE1-45A8-B3E8-F790C84CC44F"));
sb1.SetSchemaName("SubSchemaTest1");
FieldBuilder fb11 = sb1.AddSimpleField("SubFieldTest11", typeof(UV));
fb11.SetUnitType(UnitType.UT_Length);
Schema schema1 = sb1.Finish();
Entity ent1 = new Entity(schema1);
ent1.Set<UV>("SubFieldTest11", new UV(1.1, 2.2), DisplayUnitType.DUT_DECIMAL_INCHES);
SchemaBuilder sb2 = new SchemaBuilder(new Guid("2A4AAE5A-4EE1-45A8-B3E8-F790C84CC44F"));
sb2.SetSchemaName("SubSchemaTest2");
FieldBuilder fb21 = sb2.AddSimpleField("SubFieldTest21", typeof(int));
Schema schema2 = sb2.Finish();
Entity ent2 = new Entity(schema2);
ent2.Set<int>("SubFieldTest21", 1234);
SchemaBuilder sb = new SchemaBuilder(new Guid("DA4AAE5A-4EE1-45A8-B3E8-F790C84CC44F"));
sb.SetSchemaName("SchemaTest1");
FieldBuilder fb1 = sb.AddSimpleField("FieldTest1", typeof(Entity));
fb1.SetSubSchemaGUID(new Guid("1A4AAE5A-4EE1-45A8-B3E8-F790C84CC44F"));
FieldBuilder fb2 = sb.AddSimpleField("FieldTest2", typeof(Entity));
fb2.SetSubSchemaGUID(new Guid("2A4AAE5A-4EE1-45A8-B3E8-F790C84CC44F"));
Schema schema = sb.Finish();
Entity ent = new Entity(schema);
ent.Set<Entity>("FieldTest1", ent1);
ent.Set<Entity>("FieldTest2", ent2);
CachedDoc.GetElement(picked).SetEntity(ent);
trans.Commit();
}
As always, the code looks like straightforward but is not at all.
The AddSimpleField() method is supposed to add the most complicated field too (sub schema or nested Entity) as most people understand about the concept. If a field of List or Dictionary type means non-simple, a sub schema or nested Entity should be far more complicated because of the fact that it can contain anything possible including Array and Map and many such types.
The SetSubSchemaGUID() method of the FieldBuilder object has to be called to tell the FieldBuilder where the Entity instance that we are going to set come from, though the Entity itself seems to already carry the piece of information or the GUID of the sub schema (nested Entity) is no use at all. We will discuss about it a bit deeper later.
Please note it is not optional. If we do not call the FieldBuilder.SetSubSchemaGUID() for a sub schema (nested Entity) data type, an exception like the following will come up:
Autodesk.Revit.Exceptions.InvalidOperationException: SubSchema GUID required for field FieldTest1
at Autodesk.Revit.DB.ExtensibleStorage.SchemaBuilder.Finish()
…
The FieldBuilder of Entity type does not accept a unit setting and it is not optional. It is not clear what the following SetUnitType() call for the Entity type FieldBuilder will do any real harm to the Element or the whole Revit model:
…
FieldBuilder fb1 = sb.AddSimpleField("FieldTest1", typeof(Entity));
fb1.SetUnitType(UnitType.UT_Length);
…
But it is clear that an exception will be thrown out and the whole data storage process will be aborted:
Autodesk.Revit.Exceptions.InvalidOperationException: The field type does not utilize unit conversions.
at Autodesk.Revit.DB.ExtensibleStorage.FieldBuilder.SetUnitType(UnitType units)
…
A quiz: what will happen for the following AddSimpleField call for an Entity?
…
FieldBuilder fb1 = sb.AddSimpleField("1A4AAE5A-4EE1-45A8-B3E8-F790C84CC44F", typeof(Entity));
…
Here it is:
Autodesk.Revit.Exceptions.ArgumentException: The parameter fieldName is not acceptable for naming Extensible Storage objects.
Parameter name: fieldName
at Autodesk.Revit.DB.ExtensibleStorage.SchemaBuilder.AddSimpleField(String fieldName, Type fieldType)
…
So it seems some naming convention of the field name has to be applied here, but what may it be?
Cannot contain hyphens or cannot be too long?
With all these sorted out, now we can successfully write some sub schema data to an Element.
Now, let us read the same nested Entity data back from the same Element:
Reference picked = CachedUiApp.ActiveUIDocument.Selection.PickObject(ObjectType.Element, "Pick the window having the Extensible Storage");
using (Transaction trans = new Transaction(CachedDoc, "ReadDataFrom"))
{
trans.Start();
Schema sch = Schema.Lookup(new Guid("DA4AAE5A-4EE1-45A8-B3E8-F790C84CC44F"));
Entity ent = CachedDoc.GetElement(picked).GetEntity(sch);
Entity ent1 = ent.Get<Entity>("FieldTest1");
UV uvFromEnt1 = ent1.Get<UV>("SubFieldTest11", DisplayUnitType.DUT_DECIMAL_INCHES);
Entity ent2 = ent.Get<Entity>("FieldTest2");
int intFromEnt2 = ent2.Get<int>("SubFieldTest21");
string str = "UV from the sub Entity/Schema #1: \n" + string.Format("\t{0}, {1}\n", uvFromEnt1.U, uvFromEnt1.V);
str += "Integer from the sub Entity/Schema #2: \n\t" + intFromEnt2.ToString();
MessageBox.Show(str);
trans.Commit();
}
The sub schema retrieving code looks incredibly much shorter and it works! However, still a few points seem better to be clarified.
To read a nested Entity (sub schema) back, we only need to provide the field name to the Entity.Get<Entity>() generic method. The needy SubSchemaGUID when the same field being built up seems to be lost somewhere!
Each individual double type related field has to be given a DisplayUnitType regardless of the fact that it can be unit-less.
If people would like all the linear data has the same UnitType like UT_Length and the same DisplayUnitType as DUT_DECIMAL_INCHES, they have to specify it repeatedly for all double type relevant fields regardless of simple or complex.
With all the matters sorted out as discussed above and demonstrated by the code snippets, the sub schema (nested Entity) type data can finally be retrieved back.
The Revit Addin Wizard (RevitAddinWizard) is going to provide a coder to help generate Extensible Storage code.
Was the transaction necessary when reading?
Posted by: Joshnewzealand | 12/11/2019 at 02:46 AM