August 20, 2013

Determining Absolutely All Visible and Hidden Elements

One issue mentioned that was not completely answered was the following:

Question 3: I would like to export only visible objects.
Using the element IsHidden method reports whether it was hidden using the Hide in View > Elements menu option, but ignores Hide in View filters, and so does the
IsHiddenElementOrCategory method.

How can my application determine exactly what the user actually sees on the screen?
Our customers obviously expect this functionality for the export.

The cavalry is coming to the rescue here in the shape of Scott Conover of the Revit development team and Joel Spahn, software developer for
Lighting Analysts, Inc. and
ElumTools.

Scott and Joel point out that it can also be used in a more trivial fashion simply to determine the visibility of building elements, including all the different ways of controlling visibility, also covering elements in linked files.

Here is the description and part of the evolution of this project:

Goal: I want to find out if an element is visible in a view taking into account:

Everything

Whether the element is 'hidden'.

Whether the element category (or one of its parent categories) is not visible.

Visibility filters, e.g. set in visibility graphics.

Etc.

Crop Regions

View Range

Scope Box

Depth Clipping

Differences between Floor & Ceiling views

Phases

Design Options

Etc.

I have built methods to take into account whether the category is hidden.
Must I now test all the filters and filter rules associated with the given view?
Or is there a shortcut?

A filtered element collector taking a view id argument would cover all of this, which would constitute a big time saver.

That works great!

Now for the inevitable 'however':

Given an element from a linked document (found via a filtered element collector), how can I tell if the linked element is visible in a view that lives in the main document?

The view settings don’t necessarily carry over from host to link, so using FilteredElementCollector in this way won’t work.

It is not possible to create a filtered element collector where the document being filtered is a linked document, but the view being filtered is in the main document.

It may be possible to use a tool that is seemingly not appropriate to the task: CustomExporter.

This new capability processes a 3D view and has callbacks for when Links begin and end, and Elements begin and end.

So it may be possible to build a tree of elements that are considered visible in this view by using this and always returning RenderNodeAction.Skip for elements so that the child nodes (faces, meshes) are not gathered and passed.

I was able to get a CustomExporter up and running in minutes to produce a map of visible element ids that I store in a Dictionary mapping Document to HashSet of ElementId.

With larger models I was experiencing an undesirable performance hit until I started returning RenderNodeAction.Skip from the OnElementBegin method.
I can return that for all elements, since all I need is the element id in the first place.

Returning Skip immediately is the right approach to avoid generation of data you don’t care about.

There is also the small matter of keeping track which document each element belongs to. After exporting the desired views you can call the ElementVisible property to determine whether an element is visible in one of the exported views.

One other point of note here is the choice between using the Document instance itself or the Doc.PathName as a dictionary key. I chose to use the document path as the key because I wanted to persist the data for a while, and wasn’t sure how long I could hold on to the Document object before it became invalid. It would certainly be cleaner to use the Document object as the key. Also I may at some point use a HashSet of Integer instead of ElementId if I need to persist the data.

I think Document.PathName is better for a key as of now.
The Document class does override Equals and GetHashCode, but they do not seem to work correctly in all scenarios.

All in all, this should provide a complete solution to the issue #3 listed above.

It also shows the implementation of a custom export context in VB.NET.

Here is the code implementing ElementsVisibleInViewExportContext and its ElementVisible property: