Accessing Extensible Storage on OwnerFamily in Project

A couple of developers reported a problem accessing extensible storage data on families when they are loaded into a project in Revit 2015.
This problem did not occur in previous versions.

Luckily, a workaround is possible right now, designed and implemented by Alexander Ignatovich, Александр Игнатович, of

Investicionnaya Venchurnaya Companiya
.

Alexander already made a number of other complex and powerful contributions to The Building Coder in the past:

Here is Alexander’s description of the problem and workaround:

Revit 2015 Extensible Storage Access on Document OwnerFamily

The problem occurs when accessing extensible storage data on a family from a project environment via the Document.OwnerFamily property.

Let’s start off with the source code referred to below: here is
EstorageOnOwnerFamilyWorkaround.zip containing
the complete source code, Visual Studio solution and add-in manifest of the projects discussed below.

I faced a big problem migrating my add-ins working with extensible storage tied to families to Revit 2015: extensible storage on an OwnerFamily object cannot be accessed from the project family it is loaded into. Vice versa, the same thing: extensible storage created in a project, cannot be read through doc.OwnerFamily in the family editor environment.

Let’s take a closer look at this problem.
First of all, let’s create a SchemaHelper – a piece of code to simplify our next investigations.
We define a simple extensible storage schema that contains one single field Id of type Guid.
See the project named “Common”, SchemaHelper.cs, in the attached sample source code.

The method SchemaHelper.CreateSchema looks up or creates our extensible storage schema:


  private static Schema CreateSchema()
  {
    var buider = new SchemaBuilder( GetSchemaId() );
    buider.SetSchemaName( "FamilyTestSchema" );
    buider.AddSimpleField( "Id", typeof( Guid ) );
    return buider.Finish();
  }

Now, let’s implement a command that writes data to the extensible storage.
Please refer to the ExtStoragesFirstStep project.
It adds a new simple “Create test extensible storage data” command.
It writes data to the doc.OwnerFamily if the current document is a family, otherwise it writes data to all project families:


  if( doc.IsFamilyDocument )
    SetEntityToOwnerFamily( doc );
  else
    SetEntityToDocumentFamilies( doc );

So let us see how this works and what is the source of problem: open any family in the family editor, invoke the command “Create test extensible storage data”, ensure, that extensible storage entity exists in the family (for example, using RevitLookup, or just write another simple command), load the family to any other document (Project or family) and look at the loaded family in RevitLookup.
You will see no “FamilyTestSchema” extensible storage entities inside the family.

Now, invoke this command in any project document, look at the families in RevitLookup.
None of the families will contain “FamilyTestSchema” extensible storage entities.
Open any family from this project in the family editor.
There will be no entity of our schema on the document.OwnerFamily object.

The same code for Revit 2014 works fine; this is a regression in Revit 2015.
Another problem concerns getting extensible storage entity from family in dynamic updater; we also cannot read extensible storage data in families.
I suppose that when the transaction is “almost done” in the dynamic updater, Revit tries to read data from the family in the document in a similar way.

Workaround

I found a partial workaround for this problem.
In our add-ins, the most important case is when we set extensible storage entity in the family editor through the doc.Owner family and later retrieve this data in the Revit project.

The idea is to use the new FamilyLoadingIntoDocument and FamilyLoadedIntoDocument events provided by the ControlledApplication class.
I have investigated that the family being loaded always exists already in the application.Documents collection.
In the FamilyLoadingIntoDocument event we can retrieve the FamilyPath and FamilyName property values.
In the FamilyLoadedIntoDocument event handler, we are permitted to make modifications to the document.
So let me show you the code – for the full version, please refer to the module MoveExtensibleStorageDataApplication.cs in the ExtStorageSecondStep project in the attached source code:


  private static string GetFamilyFileName(
    FamilyLoadingIntoDocumentEventArgs e )
  {
    return string.Format( "{0}.rfa", e.FamilyName );
  }
 
  public static Document GetFamilyDocument(
    FamilyLoadingIntoDocumentEventArgs e )
  {
    var document = e.Document;
    var application = document.Application;
 
    var documents = application.Documents
      .Cast<Document>();
 
    string familyname = GetFamilyFileName( e );
 
    // If e.FamilyPath is not empty it is simple: 
    // just retrieve from the documents collection 
    // by document PathName;
    // otherwise, get the document by title 
    // (it can be with or without *.rfa).
 
    return string.IsNullOrWhiteSpace( e.FamilyPath )
 
      ? documents.FirstOrDefault(
        x =>
          string.IsNullOrWhiteSpace( x.PathName )
          && ( x.Title == e.FamilyName
            || x.Title == familyname ) )
 
      : documents.FirstOrDefault(
        x =>
          x.PathName == Path.Combine(
            e.FamilyPath, familyname ) );
  }
 
  private void OnFamilyLoadingIntoDocument(
    object sender,
    FamilyLoadingIntoDocumentEventArgs e )
  {
    var familyDocument = GetFamilyDocument( e );
    var family = familyDocument.OwnerFamily;
 
    var schema = SchemaHelper.GetFamilySchema();
    var entity = family.GetEntity( schema );
 
    // If the entity is valid, store it in 
    // the idToMove class property.
 
    idToMove = entity.IsValid()
      ? entity.Get<Guid>( "Id" )
      : (Guid?) null;
  }

So, if the family has extensible storage, we will read its Id from the entity and save it to the idToMove field.
Now let us write it back to the document:


  private void OnFamilyLoadedIntoDocument(
    object sender,
    FamilyLoadedIntoDocumentEventArgs e )
  {
    // The event can be cancelled or loading family 
    // can be without extensible storage entity.
 
    if( e.IsCancelled()
      || e.Status != RevitAPIEventStatus.Succeeded
      || !idToMove.HasValue )
    {
      return;
    }
 
    using( var transaction = new SubTransaction( e.Document ) )
    {
      transaction.Start();
 
      var familyId =
        e.NewFamilyId == ElementId.InvalidElementId
          ? e.OriginalFamilyId
          : e.NewFamilyId;
 
      var family = e.Document.GetElement( familyId );
      var schema = SchemaHelper.GetFamilySchema();
      var entity = new Entity( schema );
      entity.Set( "Id", idToMove.Value );
      family.SetEntity( entity );
      transaction.Commit();
    }
  }

To test this, you can manually copy the add-in manifest ExtStorageSecondStep.addin to the Revit Addins folder and restart Revit.
Repeat the load family sequence with our extensible storage entity to the document.
Now it has our entity.
We do not need to check nested families; they work fine.

In summary: the new family loading and loaded events allow you to retrieve the data from the family and write it back to the project; you may also need to rewrite your dynamic updaters that work with families – for example, in some cases, you can call e.Cancel in the FamilyLoadingIntoDocument event to prohibit a family from being loaded into the document.

Another case (when you set some data to project families) is more complex, and has no solution in the general case, but in some cases the algorithm can be adapted.
One of my add-in goals was to permit family loading to some projects, but excluding families that were already contained in the project.

The idea of the code was to calculate some “key” entity for the project – e.g., the central model path, or local path if the file is not workshared, and a couple of project information parameters – and write it to all families in the document.
In the dynamic updater, check if the key derived from the family is equal to the key derived from document.
The new approach is to get family and all nested shared families from the document in the FamilyLoadedIntoDocument event.
If they all exist, allow this action, otherwise cancel it.
Of course, it is not very good permanent solution, but it is an acceptable temporary workaround.
In this document, you can only load a family with a name that already exists; no new family can be loaded, unless it is nested.

Many thanks to Alexander for his in-depth research and crucial and creative stopgap workaround.

By the way, it also highlights the importance and usefulness of the new family loading events.

We are obviously working on an internal fix to remove the need for this as soon as possible.


Comments

7 responses to “Accessing Extensible Storage on OwnerFamily in Project”

  1. Dan Tartaglia Avatar
    Dan Tartaglia

    Hi Jeremy, is it possible to attach extensible storage to a Revit RVT Document instead of an element?
    Thanks,
    Dan

  2. Dear Dan,
    You can attach extensible storage data to any element in the Revit database?
    Is the document an element in the Revit database?
    I would say no.
    In the past, I would have said ‘use the singleton Project Information element’.
    You can use it as a representative element for the document itself.
    I have since learnt better.
    There are good reasons not to fiddle with the Project Information element in a worksharing environment.
    It would affect everybody else’s files and all elements, in a way.
    You want to limit your modifications (and therefore extensible storage) to an element that affects as few files and other elements as possible.
    A simple choice for that is the DataStorage element.
    It was designed specifically as a container for extensible storage data and nothing else, and affects no other files and elements.
    I hope this helps.
    Cheers, Jeremy.

  3. M.Vallée Avatar
    M.Vallée

    Hi Jeremy,
    It seems that there is no more issue with the Revit API shipped with Revit 2015 R2.
    Could you please confirm that it has been solved and that we no longer need to worry about ?
    Thanks a lot.
    Best regards.
    Maxime

  4. Dear Maxime,
    Yes, indeed, it should be happily fixed by now in Revit 2015 R2.
    There is apparently still an issue with Revit 2015 UR5, though, that is currently being looked at.
    Cheers, Jeremy.

  5. Hi Jeremy,
    Thanks a lot for your answer.
    BTW, if we have R2 installed, do we have to install UR5 ? I know R2 almost corresponds to UR4…, but what about UR5 ?
    Thanks.
    Maxime

  6. Dear Maxime,
    As far as I know, there are two separate UR5 patches for Revit 2015 and Revit 2015 R2.
    We are also expecting a UR6 for Revit 2015 very soon now, by the way, to incorporate the issue REVIT-57015 [Revit 2015 UR4 extensible storage fix prevents loading or editing family], which I believe was fixed by UR5 in Revit 2015 R2, but not in Revit 2015.
    Very sorry for the confusing state of affairs.
    Cheers, Jeremy.

  7. Ughh… gonna have a headache.
    Since users and the dev team all have R2 installed, everybody can update to R2 UR5 with no damage regarding this point ?

Leave a Reply to MaximeCancel reply

Discover more from Autodesk Developer Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading