Opening Geometry, Using LINQ, and Dances with Elephants

Here are two unrelated topics that came up related to openings and the use of LINQ with the Revit API.

The issue on requesting non-visible objects to be included when retrieving geometry was discussed a few times in the past, e.g. in the theoretical explanation of the geometry retrieval options and in the practical case of accessing curtain wall geometry.

Here is another neat little issue that just came up and helps throw more light on some of the variations that occur when retrieving family symbol geometry.

Question: Sometimes when I retrieve the geometry of an opening, for example from the family file M_Opening.rfa which is part of the standard Revit content, I get a solid with zero faces and edges. The Solid Edges and Faces arrays are both empty. Other times they are not.

The geometry I am trying to retrieve is the symbol geometry of the only member in the geometry element's Objects array, a geometry instance. I do not see this issue with other kinds of elements, only openings, both the ones I create myself and the default families provided with Revit.

What do I need to do to reliably retrieve the geometry of these elements?

Answer: I have seen cases in which certain elements have solid with no faces and no edges. I have always assumed that this has to do with the fact that under some circumstances, the original family symbol geometry can be used, in which case the individual family instance does not require its own individual copy. Under other circumstances, the family instance needs its own copy, because the original family symbol geometry has to be modified in some way.

For instance, consider a column that may be inserted stand-alone into a project and then can reuse the symbol geometry unmodified, or the same column connected with some beams, which may generate cut-outs or other modifications, forcing the instance to maintain its own copy of the geometry.

I described a similar situation accessing the geometry of a joined beam.

Does that apply in your case?

Response: First I edited the opening instance by changing its width and height using 'Edit Type'. The geometry returned after that had non-empty lists of edges and faces, so I guess you are onto something. I had to change the width and height by a large amount, though, because just a few millimetres did not help.

I thought I was retrieving the family symbol's geometry by querying the instance SymbolGeometry property.

Oddly, when I retrieve the geometry of the family symbol explicitly by getting the geometry of the family instance symbol, the edges and faces are still empty.

How do I retrieve what you call the "original family symbol"? Do I need to open the family symbol in a family manager or something?

I found the solution. The IncludeNonVisibleObjects property in the Options used for retrieving the geometry must be set to True.

Answer: This may be a result of the opening being a removal of geometry. In the family, the geometry consists only of four lines, defining the outline of the opening on its host, the wall.

In the project, there is no solid geometry of the element at all, as you can see by selecting the face of the opening – no volume highlights on the opening although dimensions related to the element are shown. The non-visible geometry may be used by Revit for combination and construction of the opening. This may not be fully reliable.

Another approach to finding the geometry of the opening would be to extract the geometry of the host wall, and use the Element.GetGeneratingElementIds available in Revit 2012 and later to see which faces are generated by the given opening element.

Retrieving the First Element of an Enumerable Sequence using LINQ

Markus Hannweber pointed out an interesting little possibilty in a comment on the code presented to load an Inventor ADSK component, where I retrieve the first symbol of a newly loaded family using the following lines of code:

  FamilySymbol symbol = null;
 
  foreach( FamilySymbol s in f.Symbols )
  {
    symbol = s;
    break;
  }

Markus points out that the System.Linq namespace provides the extension member First which returns the first element of any IEnumerable sequence, which would allow me to replace those lines by a single call to that extension method.

This nicely complements the suggestion by Jon Smith on using the LINQ FirstOrDefault method in the sample code on picking corners to create a floor, which avoids having to handle the exception thrown by the First method if no element is found.

Using both of these suggestions results in the following single line of code:

  FamilySymbol symbol = f.Symbols
    .Cast<FamilySymbol>()
    .FirstOrDefault<FamilySymbol>();

This is in fact not all that much shorter than the original version, and rather harder to read. Still, it is definitely worthwhile keeping an open eye on all the useful functionality provided by LINQ.

It would be shorter if the FamilySymbolSet class was not just derived from IEnumerable, as it is now, but from IEnumerable<FamilySymbol>. If that were the case, then the line above could probably be vastly simplified to

  FamilySymbol symbol = f.Symbols
    .FirstOrDefault();

The basic IEnumerable was the standard in the .NET framework 1.1. The generic IEnumerable<T> was added later, and the ungeneric IEnumerable retained for compatibility reasons. The current implementation of FamilySymbolSet seems based on the .NET framework 1.1. On the other hand, the Revit API has been in the process of phasing out all of its custom collectioon classes and replacing them with .NET standard generic collections instead for several releases now, so I guess this issue will resolve itself by itself as time goes on.

The LINQ syntax certainly makes the intent much clearer than an aborted loop does, even if it is not necessarily less code.

Anyone with the desire can of course easily add extension methods to Revit collections to allow this to be done invisibly.

Many thanks to Scott Conover for his great input on both of the issues discussed above!