Retrieve Stairs on Level

As mentioned in the discussion on selecting model elements, stairs are not represented by an own class in the Revit API, but using the generic Revit Element class instead. They do have a valid built-in category assigned to them, however, which makes it easy to retrieve them from the database and use the generic element and parameter access to retrieve and modify a lot of their data. We also discussed other aspects of stairs in the past, such as listing the railing types, material quantity extraction, and geometry retrieval.

Rocky now raised a question on retrieving stair elements from the database that allows us to take another quick look at the new Revit 2011 filtering capabilities:

Question: Will you please help me out to know how to retrieve the stairs on certain levels? E.g., if there are two stairs on the first level of the building, then on second level, how can we get these stairs?

Answer: Retrieving all the stairs on a given level is easy. We can use the stairs built-in category OST_Stairs to identify the stairs themselves, and the Element class Level property or an appropriate built-in parameter to determine what level they are on. Since the Revit filtering API is so flexible and powerful, it provides us with a number of options for the approach to use:

We have demonstrated examples of all of these in several recent posts, e.g. in our analysis of collector performance.

In all of the approaches above, one would obviously first apply a filter to check for the built-in category, for two reasons:

The first three options listed above all make use of post-processing of the results returned by the quick category filter, and are more or less equivalent in speed. Below, we present untested source code sample implementation snippets for all three of these approaches: Here is the built-in stair category constant and the element id of the level that we are searching for:

  ElementId id = level.Id;
  BuiltInCategory bic 
    = BuiltInCategory.OST_Stairs;

Here is the retrieval using explicit iteration and manual checking of a property:

  FilteredElementCollector collector
    = new FilteredElementCollector( doc );
  collector.OfCategory( bic );
  List<Element> stairs = new List<Element>();
  foreach( Element e in collector )
    if( e.Level.Id.Equals( id ) )
      stairs.Add( e );

Using LINQ, it might look like this:

  FilteredElementCollector collector
    = new FilteredElementCollector( doc );
  collector.OfCategory( bic );
  IEnumerable<Element> stairsOnLevelLinq =
    from e in collector
    where e.Level.Id.Equals( id )
    select e;

Using an anonymous method, it is even shorter:

  FilteredElementCollector collector
    = new FilteredElementCollector( doc );
  collector.OfCategory( bic );
  IEnumerable<Element> stairsOnLevelAnon =
    collector.Where<Element>( e 
      => e.Level.Id.Equals( id ) );

As said, the test of the built-in category uses a quick filter, so that is good. The post-processing is very expensive and not optimal, though.

As we demonstrated in our collector performance analysis, a parameter filter is twice as fast as post-processing the results, even though it is a slow filter and not a quick one.

To use a parameter filter on the stair elements retrieved by the category filter, we need a built-in parameter to test, instead of the Element Level property. The stair object stores its base and top levels in the built-in parameters STAIRS_BASE_LEVEL_PARAM and STAIRS_TOP_LEVEL_PARAM, so that is no problem.

Here is an untested source code example of setting up a category and a parameter filter to retrieve all stairs on a given level:

  FilteredElementCollector collector
    = new FilteredElementCollector( doc );
  collector.OfCategory( bic );
  BuiltInParameter bip
    = BuiltInParameter.STAIRS_BASE_LEVEL_PARAM;
  ParameterValueProvider provider
    = new ParameterValueProvider(
      new ElementId( bip ) );
  FilterNumericRuleEvaluator evaluator
    = new FilterNumericEquals();
  FilterRule rule = new FilterElementIdRule(
    provider, evaluator, id );
  ElementParameterFilter filter
    = new ElementParameterFilter( rule );
  return collector.WherePasses( filter );

I hope that fully answers your question, Rocky, and provides a useful working example for many others as well.