Sunday, March 28, 2010

Revit API 2011: The New Way to Find Elements

(Part of our Revit API 2011 Series)
[Editor’s Note: Updated with some late-breaking additions that will only be documented in the released documentation]

For the uninitiated, much of the core work in a Revit application involves finding elements within the Revit database. Whether it’s finding exterior walls, 3D Views, or wall types – it’s always the same concept… Doing a lookup from the Revit to get a list of elements.

This is an area that “the factory” has improved upon release-after-release… From the early days, where you always had to cycle through everything – through the newest incarnation, today. I think I’m counting right that this is the 4th generation of the element iteration API. And no more ignoring what’s new – because you’re required to use the new method in 2011!

The 2011 Approach

The factory has really created something great here – something that is mature and answers almost anything you could want in an element iteration API. And they’ve really tried to do it in a way that matched the best and most modern approaches in .NET – such as LINQ.

The fundamental concept for iteration is the FilteredElementCollector class.

Getting Started

You construct the class in one of three ways:

// get ready to filter across an entire document
FilteredElementCollector coll =
     new FilteredElementCollector( myDoc );

or

// get ready to filter across just my pre-selected set of
// elements!
FilteredElementCollector coll =
     new FilteredElementCollector( myDoc, myElementCollection );

This is great when you’d rather not search your entire model when you’ve already got the subset you want…

or

// get ready to filter across just the elements visible in a view
FilteredElementCollector coll =
    new FilteredElementCollector( myDoc, viewId );

This is equivalent to the old mechanism of the View.Elements collection – which is now gone.

These constructors just get you started… Then it’s time to move on to the filtering.

Filtering Mechanisms with FilteredElementCollection

The factory has introduced three different categories of filtering in 2011:

  • Logical (operations like AND/OR)
  • Quick (operations that are FAST)
  • Slow (operations that take a bit longer)

While these kinds of things have probably conceptually always existed, but they were found out by experimentation rather than Autodesk documenting and sharing them up front…

  • Quick
    • ElementCategoryFilter / OfCategoryId()
    • ElementTypeFilter / OfType()
    • ElementIsElementTypeFilter /
          WhereElementIsElementType()
          WhereElementIsNotElementType()
    • ElementOwnerViewFilter / OwnedByView(), WhereElementIsViewIndependent()
    • ElementDesignOptionFilter / ContainedInDesignOption()
    • ElementIsCurveDriven / WhereElementIsCurveDriven()
    • ElementStructuralTypeFilter
    • BoundingBoxContainsPointFilter
    • BoundingBoxIntersectsFilter
    • BoundingBoxIsInsideFilter
    • ExclusionFilter
    • FamilySymbolFilter
  • Slow
    • ElementParameterFilter
    • ElementLevelFilter
    • FamilyInstanceFilter
    • FamilyStructuralMaterialTypeFilter
    • PrimaryDesignOptionMemberFilter
    • StructuralInstanceUsageFilter
    • StructuralMaterialTypeFilter
    • StructuralWallUsageFilter
    • CurveElementFilter
    • RoomFilter
    • SpaceFilter
    • AreaFilter
    • RoomTagFilter
    • SpaceTagFilter
    • AreaTagFilter

These are documented further in the API help file in its own introductory section called “Element Iteration API”.

I’ll give some of the typical situations here:

If you know the type:

// we want walls
coll.OfClass( typeof(Wall) );

or if you know the category:

// we want Curtain Wall Panels:
coll.OfCategoryId( BuiltInCategory.OST_CurtainWallPanels );

Now here’s where it starts to get interesting… If you want to put different filters together, it can look something like this:

// We want door instances
coll.OfCategoryId( BuiltInCategory.OST_Doors ).OfClass( typeof(FamilyInstance));

What’s going on here? each filter ADDs requirements to the collector. And to make the whole thing more friendly towards combining, the methods return a pointer to their object, so that you can “chain” them together. For example, the code above would be equivalent to:

// we want door instances
coll.OfCategoryId( BuiltInCategory.OST_Doors);
coll.OfClass( typeof(FamilyInstance) );

We are updating our collector two times with the filter requirements.

The ElementType Topic

The ElementType / NotElementType can be a little confusing at first… There are two filters, the short-hand versions are:

  • WhereElementIsElementType()
  • WhereElementIsNotElementType()

What is ElementType? This is the renamed Symbol class in the API (re-named to match better what interactive users call things). So an “ElementType” refers to a symbol/type/definition kind of thing (a “block definition” for you AutoCAD-types). Something which is “Not an ElementType”, on the other hand – is typically something which is physically IN the model – like a specific door, line, or wall. Technically it’s a bit more broad – because it also includes items like views, etc.

That said – these two can be key filters to use to distinguish between whether you want Doors vs. Door Types, for example.

What’s the Deal with Rooms/Areas/Spaces/Tags?

Here’s a place where Autodesk’s new strategy of matching up more closely with the internal API will cause you a little bit of hassle. With Rooms/Areas/Spaces and their tags (as well as about 9 other types – see the API Introduction for the list) – there is no internal Revit type which matches them.

For example, if you look at the Room class in the 2011 API, you’ll see that it is actually a descendant of the Enclosure Class. Enclosure covers Rooms, Spaces, and Areas… Here’s the trick – Revit’s internals are obviously modeled on the “Enclosure” class – so if you’re querying by type – you can’t query on
typeof( Room ), for example.

There are some specially designed filters to compensate for this – like RoomFilter, AreaFilter, SpaceFilter, etc… These would be called as follows:

coll.WherePasses( new RoomFilter() );

The WherePasses method is another mechanism for specifying filters – you can use any filter with it.

Final Requirements of the Filter

Ok, ok – we’re almost to the payoff… But one thing I should note before we go on… If you’re used to being able to just query the whole model (for whatever reason) – you’ll find that a little bit tricky with this mechanism… Why? Because the rule is that after you construct the collector – you MUST apply at least one filter before you attempt to retrieve from it. You can’t (easily) just retrieve everything.

The closest I’ve come to it is:

// get EVERYTHING – like in the bad-old-days…
// get all the ElementTypes + all the non-ElementTypes
coll.WhereElementIsElementType().UnionWith( new
         ElementIsElementTypeFilter( false ) );


Finally – Retrieving from the Filter

Sorry to make you go through all this to get to the good part! Once you have constructed your collector and applied filter(s), then it’s time to request the contents of the collector and see what you get. Autodesk has really done a nice job of implementing some .NET 3.5 goodies here, so there are a variety of options.

coll.ToElements(); Return an IList<Element> of the contents.
coll.ToElementIds(); Return an ICollection<ElementId> of the contents.
coll.FirstElement() Return the first Element found that matches.

But that’s only the first part – the other thing I love is that the .NET 3.5 tricks that get around another annoyance – that these query routines return pointers to “Element” – which you immediately have to convert/cast down into, say “Room” to be able to easily access the properties and methods of that class.

// get all the wall types as wall type objects:
coll.OfClass (typeof(WallType));
IEnumerable<WallType> types = coll.Cast<WallType>();

foreach (WallType wt in types )
{
    // do something with the wall type
}

 

Using with LINQ

Because the FilteredElementCollector supports the IEnumerable interface – it easily  can make the jump to LINQ – being able to write full-fledged queries directly in code.

This looks something like:

FilteredElementCollector coll = new FilteredElementCollector(doc);
coll.OfClass(typeof(WallType));

var bigEnoughWallTypes =
from element in coll
where element.get_Parameter(BuiltInParameter.WALL_ATTR_WIDTH_PARAM).AsDouble() > 0.5 &&
   element.get_Parameter(BuiltInParameter.FUNCTION_PARAM).AsInteger() == 1
                         select element;

IEnumerable<WallType> types = bigEnoughWallTypes.Cast<WallType>();

ok, so the thin screen doesn’t lend itself to this… In any case – it’s not magic, it’s nothing you couldn’t have done before – but it DOES make your code a bit tighter and arguably easier to maintain because it’s clear what you’re doing (as opposed to doing the same thing in a query then loops with if statements).

The Final Analysis

In the final analysis – the new FilteredElementCollector is a fantastic new way of accessing data within Revit. It does take a little bit of adjustment, but once you’re there, I’d say it’s nicer than what existed before – and it has nice room to grow.

I still need to run the new things through a performance test (my impression is that they run faster than their 2010 equivalents – but I haven’t put a stopwatch to it yet). This post is already too long – so I’ll save that for another post…

2 comments:

David said...

I have enjoyed your blogs which are helping me with my code. Can i get your email in case i have any questions and need your help?

Matt Mason said...

sure - it's my first initial and last name at rand.com.