I already discussed the important new and powerful extensible storage mechanism, and we also explained and demonstrated it in both the DevHelp Online and Revit 2012 API webcast presentations, both of which were recorded.
I updated the sample code for the latter webcast. Here is the mainline of the Execute method:
public Result Execute( ExternalCommandData commandData, ref string message, ElementSet elements ) { UIApplication uiapp = commandData.Application; UIDocument uidoc = uiapp.ActiveUIDocument; Document doc = uidoc.Document; try { // pick an element and define the XYZ // data to store at the same time Reference r = uidoc.Selection.PickObject( ObjectType.Face, new WallFilter(), "Please pick a wall at a point on one of its faces" ); Wall wall = doc.get_Element( r.ElementId ) as Wall; XYZ dataToStore = r.GlobalPoint; Transaction t = new Transaction( doc, "Create Extensible Storage Schemata and Store Data" ); t.Start(); // store the data, and also // demonstrate reading it back StoreDataInWall( wall, dataToStore ); t.Commit(); // list all schemas in memory across all documents ListSchemas(); return Result.Succeeded; } catch( Exception ex ) { message = ex.Message; return Result.Failed; } }
It prompts you to select a wall at a specific point. The PickObject method can return both the selected wall and the picked point from one single user click. It also takes a WallFilter instance, an implementation of the ISelectionFilter interface, to restrict the user selection to walls only:
class WallFilter : ISelectionFilter { public bool AllowElement( Element e ) { return e is Wall; } public bool AllowReference( Reference r, XYZ p ) { return true; } }
The selected point is stored on the wall in a newly created extensible storage schema by the following helper method:
/// <summary> /// Create an extensible storage schema, /// attach it to a wall, populate it with data, /// and retrieve the data back from the wall. /// </summary> void StoreDataInWall( Wall wall, XYZ dataToStore ) { SchemaBuilder schemaBuilder = new SchemaBuilder( new Guid( "720080CB-DA99-40DC-9415-E53F280AA1F0" ) ); // allow anyone to read the object schemaBuilder.SetReadAccessLevel( AccessLevel.Public ); // restrict writing to this vendor only schemaBuilder.SetWriteAccessLevel( AccessLevel.Vendor ); // required because of restricted write-access schemaBuilder.SetVendorId( "ADNP" ); // create a field to store an XYZ FieldBuilder fieldBuilder = schemaBuilder .AddSimpleField( "WireSpliceLocation", typeof( XYZ ) ); fieldBuilder.SetUnitType( UnitType.UT_Length ); fieldBuilder.SetDocumentation( "A stored " + "location value representing a wiring " + "splice in a wall." ); schemaBuilder.SetSchemaName( "WireSpliceLocation" ); // register the schema Schema schema = schemaBuilder.Finish(); // create an entity (object) for this schema (class) Entity entity = new Entity( schema ); // get the field from the schema Field fieldSpliceLocation = schema.GetField( "WireSpliceLocation" ); // set the value for this entity entity.Set<XYZ>( fieldSpliceLocation, dataToStore, DisplayUnitType.DUT_METERS ); // store the entity on the element wall.SetEntity( entity ); // read back the data from the wall Entity retrievedEntity = wall.GetEntity( schema ); XYZ retrievedData = retrievedEntity.Get<XYZ>( schema.GetField( "WireSpliceLocation" ), DisplayUnitType.DUT_METERS ); }
The mainline also calls another little method ListSchemas which shows how all schemas currently loaded into Revit memory can be accessed and listed:
/// <summary> /// List all schemas in Revit memory across all documents. /// </summary> void ListSchemas() { IList<Schema> schemas = Schema.ListSchemas(); int n = schemas.Count; Debug.Print( string.Format( "{0} schema{1} defined:", n, PluralSuffix( n ) ) ); foreach( Schema s in schemas ) { IList<Field> fields = s.ListFields(); n = fields.Count; Debug.Print( string.Format( "Schema '{0}' has {1} field{2}:", s.SchemaName, n, PluralSuffix( n ) ) ); foreach( Field f in fields ) { Debug.Print( string.Format( "Field '{0}' has value type {1}" + " and unit type {2}", f.FieldName, f.ValueType, f.UnitType ) ); } } }
In the sample so far, only a simple XYZ data field is added and stored.
Here is a new question on making use of extensible storage to store a more complex data type, a dictionary mapping keys to values, in this case both represented by strings.
Question: I have added simple data to Revit elements using Extensible Storage. Now I need a schema containing a field which is a map of strings. I added a field by using
FieldBuilder.AddMapField( MyMappedField, typeof( string ), typeof( string ) );
Now comes my problem: how can I set a mapped value to such a field by using Entity.Set<???>(???)?
Similarly, how can I retrieve a mapped value from such a field?
How can I remove a mapped value?
Please can you implement such a sample?
Answer: The key, no pun intended, is to use IDictionary<> as the type you pass to the Set<> method, even though your actual implementation concrete type is Dictionary<>.
This is document in the Revit API help file entry on the Entity.Set method, but I admit that it is tricky.
By the way, this is also demonstrated by the ExtensibleStorageManager SDK sample. For instance, it includes the following code snippet:
// Note that we use IDictionary<> for // map types and IList<> for array types mySchemaWrapper .AddField<IDictionary<string, string>>( map0Name, UnitType.UT_Undefined, null ); mySchemaWrapper .AddField<IList<bool>>( array0Name, UnitType.UT_Undefined, null );
Here is a new method StoreStringMapInElement that I added to the sample above to demonstrate this:
/// <summary> /// Create an extensible storage schema specifying /// a dictionary mapping keys to values, both using /// strings, populate it with data, attach it to the /// given element, and retrieve the data back again. /// </summary> void StoreStringMapInElement( Element e ) { SchemaBuilder schemaBuilder = new SchemaBuilder( new Guid( "F1697E22-9338-4A5C-8317-5B6EE088ECB4" ) ); // allow anyone to read or write the object schemaBuilder.SetReadAccessLevel( AccessLevel.Public ); schemaBuilder.SetWriteAccessLevel( AccessLevel.Public ); // create a field to store a string map FieldBuilder fieldBuilder = schemaBuilder.AddMapField( "StringMap", typeof( string ), typeof( string ) ); fieldBuilder.SetDocumentation( "A string map for Tobias." ); schemaBuilder.SetSchemaName( "TobiasStringMap" ); // register the schema Schema schema = schemaBuilder.Finish(); // create an entity (object) for this schema (class) Entity entity = new Entity( schema ); // get the field from the schema Field field = schema.GetField( "StringMap" ); // set the value for this entity IDictionary<string, string> stringMap = new Dictionary<string, string>(); stringMap.Add( "key1", "value1" ); stringMap.Add( "key2", "value2" ); entity.Set<IDictionary<string, string>>( field, stringMap ); // store the entity on the element e.SetEntity( entity ); // read back the data from the wall Entity retrievedEntity = e.GetEntity( schema ); IDictionary<string, string> stringMap2 = retrievedEntity .Get<IDictionary<string, string>>( schema.GetField( "StringMap" ) ); }
Here is the full updated sample application ExtensibleStorage.zip including the Visual Studio solution and full source.
Many thanks to our local Revit extensible storage expert Steven Mycynek for help resolving this issue!