Modifying, Saving and Reloading Families

I recently grabbed one of those rare opportunities to do a little bit of coding myself, to answer a question on modifying and reloading a family.

More precisely, the task at hand is to modify the text note type font in all of the loaded families.

I’ll take a look at that below.

First, lets look at a simpler question, on how to

save a family after editing it
,
raised by Raunoveb on the

Revit API discussion forum
:

Saving Family after Editing with FamilyManager

Question: We are developing a solution that uploads Revit Family files to DMS server.

First we added a few shared parameters that have the information of DMS record to our families using the FamilyManager AddParameter method.

Everything works fine until we call our UploadOriginal method that attempts to save the modified family file:


  string path = Path.GetTempPath();
  string name = family.Name;
  string fName = name + ".rfa";
  string fPath = path + fName;
 
  // Revit throws an error on this line 
  // saying that Family is not editable
  // What could cause this mayhem?
  // To upload .rfa Family file I need to 
  // save it as a file first and that's what 
  // I try to do until mighty ERROR occurs
 
  Document famDoc = doc.EditFamily( family );
  famDoc.SaveAs( fPath );
  famDoc.Close( false );
 
  // application related code following...

What could be the reason of this error? How could I fix it?
Must it be overwritten like a baus (BOSS)?

Answer: I recently implemented an add-in that I discuss further down to update fonts in loaded families.

For some of the loaded families, EditFamily threw that same exception saying, “Family is not editable”.

I added an exception handler to skip them:


  try
  {
    r1.FamilyDocument
      = famdoc
      = doc.EditFamily( f );
  }
  catch( Autodesk.Revit.Exceptions.ArgumentException ex )
  {
    r1.Skipped = true;
    results.Add( r1 );
    Debug.Print( "Family '{0}': {1}", f.Name, ex.Message );
    continue;
  }

Here is an excerpt of the add-in log; the families marked ‘skipped’ are the ones I mean:

Reload family report

I simply assumed that is normal.

P.S. cool video :-)

Question: I managed to skip those families by simply adding an if(family.IsEditable) block around the code.
Unfortunately for us these families we try to process must be processed so we can’t just skip them.

I’ve had 2 ideas that could explain those errors:

FamilyManager (fm) somehow locks the family currently open in Family Document and only allows us to edit the family after it’s released the family.
However even after fm.Dispose() the EditFamily method threw that exception.

(This idea could be related to first idea).
One cannot just simply write a family to file just after the shared parameters have been changed/added.
A “save” command must be somehow executed in order to “unlock” this family.
However once again I found no methods in either FamilyManager or Family class that would allows us to do that.

I have danced around this problem for 3 days and have ran out of ideas.
Any help “unlocking” these families would be greatly appreciated.

Answer: Thank you very much for pointing out the IsEditable predicate.
I successfully replaced my exception handler by simply checking that instead.

Regarding your locked families, have you tried ensuring that absolutely no other documents are open, only the one and only family that you are trying to modify?

Question: Thanks for your comment about any other open documents.
I suddenly realized that in my main program I already had FamilyDocument open.
When I used UploadOriginal() method I gave doc as an input and then tried:


  var famDoc = doc.EditFamily( family );
  famDoc.SaveAs( path );
  famDoc.Close( false );

That created another Family Document instance of the same family and that’s why it threw this “Family not editable” exception. I changed it to:


  doc.SaveAs( path );
 
  // Can't close it since I have this family view 
  // open in Revit and API doesn't have permission 
  // to close visibly open documents in Revit.
 
  //doc.Close(false);

However now I’ve got problems with getting the Family.Name value from doc.OwnerFamily.Name.
It always returns “” and all the files saved look like this “/folder/.rfa”, when they should look something like this “/folder/NightLamp.rfa”.

Answer: Cool.

Progress.

How about using the document title instead of the family name?

Question: Using doc.Title did the trick. However it’s weird that OwnerFamily.Name returned blank response.

Thanks for everything. And since your second answer provided most help regarding to the main question I’ll mark that one as a solution.

Answer: Cooler still.

That was a quick solution.

Thank you for marking the solution and for the interesting discussion.

Replacing all Text Note Type Fonts in all Loaded Families

With that little intermezzo out of the way, we get to the real thing:

Question: Update text font style property.

I am trying to update the text font property for the active project and also update all the families loaded in that project, i.e. the active document.

The first part works fine, updating the text property for the active project.

I am having trouble figuring out how to update the text property for all the families loaded into the active project, though.

This is my current coding attempt:


  Document doc = commandData.Application
    .ActiveUIDocument.Document;
 
  FilteredElementCollector collectorUsed
    = new FilteredElementCollector( doc );
 
  collectorUsed.OfClass( typeof( Family ) );
 
  foreach( Family f in collectorUsed )
  {
    string name = f.Name;
    Document famdoc = doc.EditFamily( f );
 
    FilteredElementCollector famcollectorUsed
      = new FilteredElementCollector( famdoc );
 
    ICollection<ElementId> textNoteTypes
      = famcollectorUsed.OfClass( typeof( TextNoteType ) )
        .ToElementIds();
 
    foreach( ElementId textNoteTypeId in textNoteTypes )
    {
      Element ele = doc.GetElement( textNoteTypeId );
      foreach( Parameter p in ele.Parameters )
      {
        if( p.Definition.Name == "Text Font" )
        {
          using( Transaction tranew
            = new Transaction( doc ) )
          {
            tranew.Start( "Update" );
            p.Set( "Arial Black" );
            tranew.Commit();
          }
        }
      }
    }
  }

Answer:

I discussed the topic of reloading families on The Building Coder in 2011:

Some things have changed a little bit since then.

The main principles remain the same, however.

I looked at the help file on the

Text Note Type Properties
and
see the Text Font property that you wish to change, and that looks fine.

There is no need to loop through all the element parameters and match individual strings to find the parameter you are after, however.

You can use the GetParameters or LookupParameter method instead. You should obviously no longer use the obsolete get_Parameter method, if you can avoid it.

By the way, you should also not use the parameter name to identify it if it is possible to use a built-in parameter enumeration value instead, since the latter is both language independent, more efficient, and guaranteed to return a unique result.

For the sake of efficiency, you might want to check whether the current font property setting already as the desired value before you open an extra new transaction and modify it.

Just as you say, though, even after you have modified the text note types in the family document and committed the transactions, the changes are still not reflected in the container project document.

This is actually clearly documented in the Revit API help file RevitAPI.chm, in the remarks on the Document.EditFamily method:

Remarks

This creates an independent copy of the family for editing. To apply the changes back to the family stored in the document, use the LoadFamily overload accepting IFamilyLoadOptions.

This method may not be called if the document is currently modifiable (has an open transaction) or is in a read-only state. The method may not be called during dynamic updates. To test the document’s current status, check the values of IsModifiable and IsReadOnly properties.

I also pointed this out when discussing some

changes in calling the EditDocument method
back
in the Revit 2013 timeframe.

There are some further important considerations when

reloading a family
.

Actually, here is a complete list of discussions related to reloading families or mentioning the IFamilyLoadOptions interface that might also be useful:

Based on the information provided there, we have to explicitly reload all the modified families after updating their text note type font properties by calling the LoadFamily method on each, and specify an appropriate IFamilyLoadOptions interface implementation when doing so. Here is a suitable one, updated from the discussions listed above:


  class JtFamilyLoadOptions : IFamilyLoadOptions
  {
    public bool OnFamilyFound(
      bool familyInUse,
      out bool overwriteParameterValues )
    {
      overwriteParameterValues = true;
      return true;
    }
 
    public bool OnSharedFamilyFound(
      Family sharedFamily,
      bool familyInUse,
      out FamilySource source,
      out bool overwriteParameterValues )
    {
      source = FamilySource.Family;
      overwriteParameterValues = true;
      return true;
    }
  }

Another issue to be aware of is that you are not allowed to perform any element deletions while iterating over the results of a filtered element collector, or Revit will throw an InvalidOperationException saying ‘The iterator cannot proceed due to changes made to the Element table in Revit’s database (typically, This can be the result of an Element deletion).’

That forced me to postpone the family reloading operation and move it out of the collector iteration loop, where it was originally situated.

I implemented a new add-in named SetTextFontInFamilies to demonstrate all this.

As usual, in order to understand and understand what it really does, the logging and reporting code exceeds the actual task implementation.

It keeps track of all the families processed.

Some families are skipped, because the EditFamily method throws an ArgumentException saying ‘This family is not editable.’

If not skipped, it also records all the text note types processed, and how many of them actually require a modification of the font.

Here is the main result logging implementation class:


  /// <summary>
  /// Logging helper class to keep track of the result
  /// of updating the font of all text note types in a
  /// family. The family may be skipped or not. If not,
  /// keep track of all its text note types and a flag 
  /// for each indicating whether it was updated.
  /// </summary>
  class SetTextFontInFamilyResult
  {
    class TextNoteTypeResult
    {
      public string Name { get; set; }
      public bool Updated { get; set; }
    }
 
    /// <summary>
    /// The Family element name in the project database.
    /// </summary>
    public string FamilyName { get; set; }
 
    /// <summary>
    /// The family document used to reload the family.
    /// </summary>
    public Document FamilyDocument { get; set; }
 
    /// <summary>
    /// Was this family skipped, e.g. this family is not editable.
    /// </summary>
    public bool Skipped { get; set; }
 
    /// <summary>
    /// List of text note type names and updated flags.
    /// </summary>
    List<TextNoteTypeResult> TextNoteTypeResults;
 
    public SetTextFontInFamilyResult( Family f )
    {
      FamilyName = f.Name;
      TextNoteTypeResults = null;
    }
 
    public void AddTextNoteType(
      TextNoteType tnt,
      bool updated )
    {
      if( null == TextNoteTypeResults )
      {
        TextNoteTypeResults
          = new List<TextNoteTypeResult>();
      }
      TextNoteTypeResult r = new TextNoteTypeResult();
      r.Name = tnt.Name;
      r.Updated = updated;
      TextNoteTypeResults.Add( r );
    }
 
    int NumberOfUpdatedTextNoteTypes
    {
      get
      {
        return null == TextNoteTypeResults
          ? 0
          : TextNoteTypeResults
            .Count<TextNoteTypeResult>(
              r => r.Updated );
      }
    }
 
    public bool NeedsReload
    {
      get
      {
        return 0 < NumberOfUpdatedTextNoteTypes;
      }
    }
 
    public override string ToString()
    {
      // FamilyDocument.Title
 
      string s = FamilyName + ": ";
 
      if( Skipped )
      {
        s += "skipped";
      }
      else
      {
        int nTotal = TextNoteTypeResults.Count;
        int nUpdated = NumberOfUpdatedTextNoteTypes;
 
        s += string.Format(
          "{0} text note types processed, "
          + "{1} updated", nTotal, nUpdated );
      }
      return s;
    }
  }

I actually ended up using it for more than just logging the results, once I discovered that we need to terminate the first iteration over the filtered element collector before we can apply the modifications.
Therefore, by tracking the font modifications made, this class also keeps track of which families need reloading at all.

The main Execute method making use of this, determining and iterating over all the loaded families, modifying all their text note type fonts and reloading them afterwards, ends up looking like this:


  public Result Execute(
    ExternalCommandData commandData,
    ref string message,
    ElementSet elements )
  {
    UIApplication uiapp = commandData.Application;
    UIDocument uidoc = uiapp.ActiveUIDocument;
    Application app = uiapp.Application;
    Document doc = uidoc.Document;
 
    FilteredElementCollector families
      = new FilteredElementCollector( doc )
        .OfClass( typeof( Family ) );
 
    List<SetTextFontInFamilyResult> results
      = new List<SetTextFontInFamilyResult>();
 
    Document famdoc;
    SetTextFontInFamilyResult r1;
    bool updatedTextNoteStyle;
 
    foreach( Family f in families )
    {
      r1 = new SetTextFontInFamilyResult( f );
 
      bool updatedFamily = false;
 
      // Using exception handler.
 
      //try
      //{
      //  r1.FamilyDocument
      //    = famdoc
      //    = doc.EditFamily( f );
      //}
      //catch( Autodesk.Revit.Exceptions.ArgumentException ex )
      //{
      //  r1.Skipped = true;
      //  results.Add( r1 );
      //  Debug.Print( "Family '{0}': {1}", f.Name, ex.Message );
      //  continue;
      //}
 
      // Better: test IsEditable predicate.
 
      if( f.IsEditable )
      {
        r1.FamilyDocument
          = famdoc
          = doc.EditFamily( f );
      }
      else
      {
        r1.Skipped = true;
        results.Add( r1 );
        Debug.Print( "Family '{0}' is not editable", f.Name );
        continue;
      }
 
      FilteredElementCollector textNoteTypes
        = new FilteredElementCollector( famdoc )
          .OfClass( typeof( TextNoteType ) );
 
      foreach( TextNoteType tnt in textNoteTypes )
      {
        updatedTextNoteStyle = false;
 
        // It is normally better to use the built-in
        // parameter enumeration value rather than
        // the parameter definition display name.
        // The latter is language dependent, possibly
        // returns multiple hits, and uses a less 
        // efficient string comparison.
 
        //Parameter p2 = tnt.get_Parameter( 
        //  _parameter_bip );
 
        IList<Parameter> ps = tnt.GetParameters(
          _parameter_name );
 
        Debug.Assert( 1 == ps.Count,
          "expected only one 'Text Font' parameter" );
 
        foreach( Parameter p in ps )
        {
          if( _font_name != p.AsString() )
          {
            using( Transaction tx
              = new Transaction( doc ) )
            {
              tx.Start( "Update Text Font" );
              p.Set( _font_name );
              tx.Commit();
 
              updatedFamily
                = updatedTextNoteStyle
                = true;
            }
          }
        }
        r1.AddTextNoteType( tnt, updatedTextNoteStyle );
      }
      results.Add( r1 );
 
      // This causes the iteration over the filtered 
      // element collector to throw an 
      // InvalidOperationException: The iterator cannot 
      // proceed due to changes made to the Element table 
      // in Revit's database (typically, This can be the 
      // result of an Element deletion).
 
      //if( updatedFamily )
      //{
      //  f2 = famdoc.LoadFamily(
      //    doc, new JtFamilyLoadOptions() );
      //}
    }
 
    // Reload modified families after terminating 
    // the filtered element collector iteration.
 
    IFamilyLoadOptions opt
      = new JtFamilyLoadOptions();
 
    Family f2;
 
    foreach( SetTextFontInFamilyResult r in results )
    {
      if( r.NeedsReload )
      {
        f2 = r.FamilyDocument.LoadFamily( doc, opt );
      }
    }
 
    TaskDialog d = new TaskDialog(
      "Set Text Note Font" );
 
    d.MainInstruction = string.Format(
      "{0} families processed.", results.Count );
 
    List<string> family_results
      = results.ConvertAll<string>(
        r => r.ToString() );
 
    family_results.Sort();
 
    d.MainContent = string.Join( "rn",
      family_results );
 
    d.Show();
 
    return Result.Succeeded;
  }

Here is a report of the result, displayed by this command in a Revit task dialogue:

Reload family report

This report omits the list of text note types processed within each family, although we actually do keep track of them internally as well in the TextNoteTypeResult and SetTextFontInFamilyResult classes.

Note that I go to the very slight extra effort of sorting the results alphabetically for the convenience of the human reader.

Also note that the task dialogue very kindly adds a scroll bar automatically.

Finally, note that running this command even in a minimal new empty Revit project takes rather a long time to complete.

The complete implementation and Visual Studio solution including the add-in manifest is provided in the
SetTextFontInFamilies GitHub repository.


Comments

8 responses to “Modifying, Saving and Reloading Families”

  1. Hi, nice routine, I want to try it out, and I am a bit too new to coding to understand everything but the foolowing:
    “You can use the GetParameters or LookupParameter method instead. You should obviously no longer use the obsolete get_Parameter method, if you can avoid it.”
    when I get to build your solution in VS, I get the error: “Error 1 ‘Autodesk.Revit.DB.TextNoteType’ does not contain a definition for ‘GetParameters’ and no extension method ‘GetParameters’ accepting a first argument of type ‘Autodesk.Revit.DB.TextNoteType’ could be found (are you missing a using directive or an assembly reference?)”
    if I missed a reference, could you point me where I should look at, please.
    I might try to use the routine to purge my families… I can’t wait/
    thanks

  2. Dear Jean-Marc,
    I have no idea how you managed to produce that error message.
    The GetParameters method was introduced in Revit 2015, so it does not exist in previous versions.
    It looks to me as if you are referencing an earlier version of the Revit API assembly, e.g. from Revit 2014.
    Cheers, Jeremy.

  3. indeed///
    is there a way to make it work in 2014?
    thks

  4. Dear Jean-Marc,
    Sure.
    In Revit 2014, you can use get_Parameter.
    Cheers, Jeremy.

  5. thank you Jeremy

  6. Jean-Marc Couffin Avatar
    Jean-Marc Couffin

    Hi Jeremy,
    I got it to work in 2015.
    it takes a while to process, even on a OutOfTheBox template. but it works.
    that is a big headstart for what I would like to achieve: open families of a project and purge them and then reload them…
    I have seen ways to ‘purge’/delete items in the families doing some filtering per category getting reverse Ids list of families with instances in the family/or model through Harry Mattison.
    but is there a way, or a better workaround? because filtering each category one by on seems a big task to ask for each family.
    the process of purging these family would take forever (but that is just a guess)
    is there a way to access the built in purge command in revit though the API. I have looked through the API doc of 2015, and I could not find anything relevant besides an IsPurgeable member i think

  7. Dear Jean-Marc,
    Congratulations on getting it to work!
    Yes, that would be cool. Unfortunately, this functionality is currently not accessible. We have an open wish list item for this access…
    Cheers, Jeremy.

  8. Hi Jeremy,
    I found this code:
    public void PlaceStiffenerOnWallFace(Autodesk.Revit.DB.Document doc, Wall wall)
    {
    // The structural stiffeners family type is compatible with line-based face placement
    FilteredElementCollector fsCollector = new FilteredElementCollector(doc);
    fsCollector.OfClass(typeof(FamilySymbol)).OfCategory(BuiltInCategory.OST_StructuralStiffener);
    FamilySymbol stiffenerSymbol = fsCollector.FirstElement() as FamilySymbol;
    // The only way to get a Face to use with this NewFamilyInstance overload
    // is from Element.Geometry with ComputeReferences turned on
    Face face = null;
    Options geomOptions = new Options();
    geomOptions.ComputeReferences = true;
    GeometryElement wallGeom = wall.get_Geometry(geomOptions);
    foreach (GeometryObject geomObj in wallGeom)
    {
    Solid geomSolid = geomObj as Solid;
    if (null != geomSolid)
    {
    foreach (Face geomFace in geomSolid.Faces)
    {
    face = geomFace;
    break;
    }
    break;
    }
    }
    // Generate line for path
    BoundingBoxUV bbox = face.GetBoundingBox();
    UV lowerLeft = bbox.Min;
    UV upperRight = bbox.Max;
    double deltaU = upperRight.U – lowerLeft.U;
    double deltaV = upperRight.V – lowerLeft.V;
    double vOffset = deltaV * 0.80; // 80% up the wall face
    UV firstPoint = lowerLeft + new UV(deltaU * 0.30, vOffset);
    UV lastPoint = lowerLeft + new UV(deltaU * 0.70, vOffset);
    Line line = Line.CreateBound(face.Evaluate(firstPoint), face.Evaluate(lastPoint));
    doc.Create.NewFamilyInstance(face, line, stiffenerSymbol);
    }
    is there a way to place PlaceStiffener On column Face, I try but the new element host = null

Leave a Reply

Discover more from Autodesk Developer Blog

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

Continue reading