Quicker Filtering by Element Name

Quicker Filtering by Element Name

timothylogan2019
Participant Participant
9,237 Views
7 Replies
Message 1 of 8

Quicker Filtering by Element Name

timothylogan2019
Participant
Participant

I wanted to check if anyone had a quicker way of gathering elements in a view where the element name matches a given string.  For instsance, I have a plugin I developed a couple of years ago to add some no plot functionality to Revit printing, but it can be horribly slow.  I believe I've tracked the slowness down to finding elements with "NPLT" in the name.  It takes about as long to find the elements as it takes to print, easily doubling the print time.  Currently the search for elements looks something like this:

 

// Find any NP elements in the view
IList<ElementId> npSheetElements = new List<ElementId>();
FilteredElementCollector npElemCollector = new FilteredElementCollector(doc, view.Id);
foreach (Element elem in npElemCollector)
{
    if (elem.Name.Contains("NPLT"))
    npSheetElements.Add(elem.Id);

}

I was looking to see if I could find some quick filters to handle it and hopefully speed it all up, but I didn't see anything that I felt was related to filtering by element name.

 

Thanks,

Tim

0 Likes
Accepted solutions (2)
9,238 Views
7 Replies
Replies (7)
Message 2 of 8

Moustafa_K
Advisor
Advisor

try this statment

            Element Ele
              = new FilteredElementCollector(m_doc, m_doc.ActiveView.Id).WhereElementIsElementType().ToElements().Where(o => o.Name == "Given string").First();

but make sure to add 

using System.Linq;

at the using namespaces

Moustafa Khalil
Cropped-Sharp-Bim-500x125-Autodesk-1
Message 3 of 8

jeremytammik
Autodesk
Autodesk
Accepted solution

Dear Tim,

 

Thank you for your very valid query.

 

Dear Moustafa,

 

Thank you very much for jumping in and your helful suggestion!

 

Both contributions illustrate some of the points I made recently discussing

 

  • Revit element filter classification
  • Use of LINQ with filtered element collectors
  • Use of the ToElements method

 

http://thebuildingcoder.typepad.com/blog/2015/12/quick-slow-and-linq-element-filtering.html

 

Possibly that article prompted Tim's question in the first place?

 

Let me start by suggesting some improvements to Moustafa's solution, because his sample code includes steps that I would classify as completely unneccesary performance penalties.

 

1. Calling ToElements is often unneccesary, and in this case particularly so.

 

It creates a copy of all the data retrieved by the preceding collector steps, wasting both time and memory space. For more details, please refer to the article above.

 

2. Where and First can be combined, e.g. as

 

FirstOrDefault<Element>( o => o.Name == "Given string" )

 

That might return faster and therefore turn out to be more efficient than performing them as two separate consecutive steps, because it should return a result as soon as the first hit is found, unlike Moustafa's code.

 

Now to answer Tim's original question:

 

The element name is always accessible in two ways: via the .NET Name property on the Element class, and via a built-in parameter value.

 

If you use the parameter value to read the element name, you can encapsulate the name check in a parameter filter, which is a quick filter, which is what you are asking for.

 

This will always double performance, at least, since the string comparison happens in the Revit internal memory and saves all the .NET marshalling and data transfer out of Revit to the .NET add-in.

 

I discussed this very question way back in 2010, using the Revit 2011 API:

 

http://thebuildingcoder.typepad.com/blog/2010/06/element-name-parameter-filter-correction.html

 

The samples presented there are maintained in The Building Coder samples:

 

https://github.com/jeremytammik/the_building_coder_samples

 

Thanks to this, an up-to-date version is available from GitHub.

 

All of the statements made above come with no guarantee and are only guesses, as long as they have not been explicitly and carefully benchmarked.

 

That is left as an exercise to the interested reader.

 

I hope this helps.

 

Have fun!

 

Best regards,

 

Jeremy



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

Message 4 of 8

jeremytammik
Autodesk
Autodesk

Tim,

 

A further extremely simple and tremendously efficient enhancement that you should make to you code is to add other filters before checking the element name!

 

Your code retrieves the element name for all elements of all kinds from the database. Well, from the view, at least.

 

You can add any number of other quick filters beforehand, e.g., to suppress ElementType objects, filter for certain categories, filter for certain classes, etc.

 

That will probably speed things up by an order of magnitude or two right there.

 

Cheers,

 

Jeremy



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

Message 5 of 8

timothylogan2019
Participant
Participant

Thanks for the tips Moustafa and Jeremy.  I think I've let myself get bad habits by not worrying performance in the past.  I'll try some of the things you suggest and report back what I find.

 

-Tim

0 Likes
Message 6 of 8

Moustafa_K
Advisor
Advisor
Thanks Jeremy for this thorough article you linked. I have read it and really impressed. I need to update my library data base accordingly.
Moustafa Khalil
Cropped-Sharp-Bim-500x125-Autodesk-1
0 Likes
Message 7 of 8

timothylogan2019
Participant
Participant
Accepted solution

Again, thanks to both of you for the advice.  Reporting back on what I've found, while my filtering/selection method was rather slow, it's not necessarily the problem with the process.  I tried three variations of finding the elements, with little difference in time.  To hopefully clarify what is happening and why I thought the time crunch was only realted to finding the elements of a given name, here's a simplified or idealized process of what's happening:

 

  1. A set of sheets is printed, captured by the printing event.
  2. Find all views placed on the sheet
  3. Foreach view (including the sheet itself) do the following: Turn off certain subcategories, find and hide elements with a matching name
  4. Print the sheets
  5. Unhide all elements and turn subcategories back on from step 3 in the printed event.

So if I run through everything, but at step 3 I only turn off the subcategories but do not search for elements and hide them, it will run through and take maybe 10 seconds longer than printing by itself.  So I set up a couple of timers to get the time it took to find the elements and a separate timer to tell me how long it took to hide the elements.  The finding elements part always took a considerable amount of time, say a little under 2 minutes for 10 sheets (about the same amount of time to actually print those 10 sheets), whereas the hiding and turning off subcategories and all that only took about 10 seconds.

 

Nothing I did seemed to make any considerable difference in that finding time.  Here's the three different ways I went about trying to find the elements.  Notice that I'm collecting all elements that match, not necessarily the first one.  Not sure how much impact this has on time, but I'll hazard a guess and say not a lot.

 

 

// Find any NP elements in the view
List<ElementId> npSheetElements = new List<ElementId>();
if (searchType == 0) // Slow Way of doing things
{
    FilteredElementCollector viewCollector = new FilteredElementCollector(doc, view.Id);
    foreach (Element elem in viewCollector)
    {
        if (elem.Name.Contains("NPLT"))
            npSheetElements.Add(elem.Id);
    }
}
else if (searchType == 1)  // Linq
{
    IEnumerable<ElementId> npElemCollector = new FilteredElementCollector(doc, view.Id).ToElementIds().Where(o => doc.GetElement(o).Name.Contains("NPLT"));
       npSheetElements.AddRange(npElemCollector);
}
else if (searchType == 2) // Parameter Filter
{
    // NOTE:
    // While the other two methods are a little more broad in what they select
// I set this to only look for TextNotes to try and speed it up as much as possible,
// mostly as a test of speed. FilterableValueProvider provider = new ParameterValueProvider(new ElementId(BuiltInParameter.ALL_MODEL_TYPE_NAME)); FilterRule rule = new FilterStringRule(provider, new FilterStringContains(), npIdentifier, true); ElementParameterFilter epf = new ElementParameterFilter(rule, false); IEnumerable<ElementId> npText = new FilteredElementCollector(doc, view.Id).OfCategory(BuiltInCategory.OST_TextNotes).WherePasses(epf).ToElementIds(); npSheetElements.AddRange(npText); }

 

So after having no luck with any of this, I decided to try just making a macro real quick that would just search for elements in a similar fashion, but not do anything with the elements.  I set it up so that I could select some sheets from the project browser, and then use those selected sheets to act on, iterating through all of the views on the sheets and trying to find elements in the same way as I show above.  The first time I ran the macro, it took as long to run as I would expect based on the previous tests with a plugin. 

 

25_Sheets_FirstTime.png

 

So the image shows how long it took to iterate through 25 sheets and their views to find TextNote elements using the last option in the code above (parameter filter).  Not quite what I was hoping for since there's no transaction or anything else really happening.

 

25_Sheets_SecondTime.png

 

So I ran the macro again, and to my surprise it runs it in a tiny fraction of the time.  Almost 3 minutes to run it the first time, 7 hundredths of a second the next time.  This is definitely more along the lines of what I was hoping for, so I ran the macro a few more times just to see if it was some kind of fluke.  Each subsequent time I ran it, it finished in well under a second.

 

So then for my due dilligence I ran it on a different set of sheets, and again it took several minutes to complete. The second time I ran it with this new set of sheets it ran in under a second once more.  So I'm thinking I've been looking in the wrong place the whole time, as it seems to be more of an issue with getting a view that's not open in the model than it is about finding the elements.  Even though the view doesn't get opened (at least from the UI stand point), any subsequent time you try to act on those views it happens relatively quickly.  Selecting all drawings from my first and second attempts, it will iterate through them all quite quickly still, so it's not the mere act of repeating the same command on the same set of views, but repeating the command on any previously acted on set of views.

 

printingButNot.png

 

The first time I run the macro on any set of views, I see down in the task bar a message showing that the sheet is being printed, even though it is not.  Definitely something I've seen before when opening a view through the UI, so maybe there's not a way around this. 

 

findAll.png

 

I suspected that trying to find the elements in a specific view will force the view to open, incurring the time penalty that started this thread, whereas if I was just looking for elements in the document as a whole it wouldn't need to open the view.  So I modified the macro and just had it look for the TextNotes with the ElementParameterFilter of the document as a whole rather than the view.  It ran in 0.42s as shown above.

 

So that gave me an idea: What if I just find all of the elements in the document that match and try to hide them in each view regardless of whether they're in the view or not?  So I modified the original plugin that started this, and just set it to find a list of all elements that match at the beginning of the printing event and pass this list of elements to each view.

 

updatedSearch.png

 

The Find Time is not really accurate, as this is not reporting the new filter but the original one that's since been commented out.  All of the other times shown should be accurate though, most notably the Print Time for printing the sheets and the Total Time which includes everything from when the printing event fires to when the printed event finishes. So now isntead of having a 200% increase in print time, it's only about a 10% increase.

 

And just for the sake of completion, here's the three selection methods times to find the elements in the same 10 sheets that started all of this:

 

1. My original filter/selection method (searchType == 0 in the code above):

searchType0.png

 

2. Using Linq to filter/select (searchType == 1 in the code above):

searchType1.png

 

3. Using ElementParameterFilter (searchType == 2 in the code above):

searchType2.png

 

So this clearly shows that the ElementParameterFilter is considerably faster than either Linq or using a foreach loop on everything, finishing in less than 10% of the time.

 

Thanks again for the help to both of you.

 

-Tim

Message 8 of 8

Anonymous
Not applicable
Nice work and thanks for sharing your results! Very interesting stuff.
0 Likes