Selecting Model Elements

We repeatedly looked at the

selection of model elements
,
and in the discussion on

material quantity extraction
I
mentioned the idea that one possible additional improvement might be to make use of the Category.HasMaterialQuantities property.

The topic keeps returning, and now it arose again in a question from Konstanty Seniut.
I have updated the answer after the initial posting to add some

later notes
by
Scott Conover, so that they are not missed.
To see Scott’s notes and explanations in full, please refer to the

follow-up post
.

Question: I would like to retrieve all elements from the model, for example floors, walls, windows, lines, but ignoring all views, levels and etc.
For now my solution is simply to make list of all the categories of the things that I don’t want to retrieve.
Is there any easier way to achieve this?

Currently, the following approach is working for me:


Dictionary<string, Category> GetAllCategories(
  List<Document> allDocuments )
{
  Dictionary<string, Category> categories
    = new Dictionary<string, Category>();
 
  Dictionary<int, Element> elemnts
    = new Dictionary<int, Element>();
 
  foreach( Document doc in allDocuments )
  {
    FilteredElementCollector collector
      = new FilteredElementCollector( doc );
 
    IList<Autodesk.Revit.DB.Element> found
      = collector
        .WhereElementIsNotElementType()
        .WhereElementIsViewIndependent()
        .WherePasses( new LogicalOrFilter(
          new ElementIsElementTypeFilter( false ),
          new ElementIsElementTypeFilter( true ) ) )
        .ToElements();
 
    var disElems = (from elem in found select elem)
      .Distinct();
 
    foreach( Element element in disElems )
    {
      if( element.Category != null )
      {
        if( element.Parameters.Size > 0 )
        {
          if( element.PhaseCreated != null )
          {
            if( !categories.ContainsKey(
              element.Category.Name ) )
            {
              categories.Add(
                element.Category.Name,
                element.Category );
 
              elemnts.Add(
                element.Category.Id.IntegerValue,
                element );
            }
          }
          else if( element.Location != null )
          {
            LocationPoint point = null;
            LocationCurve curve = null;
            try
            {
              point = element.Location
                as LocationPoint;
            }
            catch { }
 
            try
            {
              curve = element.Location
                as LocationCurve;
            }
            catch { }
 
            if( curve != null || point != null )
            {
              if( !categories.ContainsKey(
                element.Category.Name ) )
              {
                categories.Add(
                  element.Category.Name,
                  element.Category );
 
                elemnts.Add(
                  element.Category.Id.IntegerValue,
                  element );
              }
            }
          }
        }
      }
    }
  }
  return categories;
}

Is this a good solution?
What do you think?

Answer: As said, I presented one approach in the

selection of model elements
.
Scott

later pointed out

that that solution appends filters to each other one at a time.
The new Revit 2011 LogicalOrFilters support more than two inputs, so it should actually be updated to make more efficient use of 2011 filtering.

The other approach I mentioned, based on the Category.HasMaterialQuantities property, allows a much shorter implementation like this:


[Transaction( TransactionMode.ReadOnly )]
[Regeneration( RegenerationOption.Manual )]
public class Lab2_2_ModelElements : IExternalCommand
{
  public Result Execute(
    ExternalCommandData commandData,
    ref string message,
    ElementSet elements )
  {
    UIApplication app = commandData.Application;
    Document doc = app.ActiveUIDocument.Document;
 
    FilteredElementCollector collector
      = new FilteredElementCollector( doc )
        .WhereElementIsNotElementType();
 
    List<string> a = new List<string>();
 
    foreach( Element e in collector )
    {
      //  && null != e.Materials
      //  && 0 < e.Materials.Size
 
      if( null != e.Category
        && e.Category.HasMaterialQuantities )
      {
        a.Add( string.Format(
          "Category={0}; Name={1}; Id={2}",
          e.Category.Name, e.Name,
          e.Id.IntegerValue ) );
      }
    }
 
    LabUtils.InfoMsg(
      "Project contains {0} model element{1}{2}", a );
 
    return Result.Failed;
  }
}

Note that this approach may not retrieve all desired elements, as Scott explains in his

update notes
.

Which approach to use, or how to combine them, depends on your exact needs, obviously.

Some notes on your implementation:

  • I find the check for parameters interesting, and also checking the PhaseCreated and Location properties.
  • I would recommend you never to use an

    exception handler
    to
    check for a valid condition, if you can avoid it.

    Exceptions should always be exceptional
    ,
    for handling unexpected errors.
  • Scott

    later pointed out

    that the use of the WherePasses clause is totally unnecessary and should be removed.

Response: The additional check for Category.HasMaterialQuantities suits me well for now.
Thank you very much!

The first approach you suggested in the blog is very good; in the beginning of solving this problem I had a similar idea to make list of redundant categories, but I wanted a simpler approach.

My final solution for now is:


private void GetAllCategories(
  List<Document> allDocuments )
{
  Dictionary<string, Category> categories
    = new Dictionary<string, Category>();
 
  List<Element> elements = new List<Element>();
 
  foreach( Document doc in allDocuments )
  {
    FilteredElementCollector collector
      = new FilteredElementCollector( doc );
 
    collector
      .WhereElementIsNotElementType()
      .WhereElementIsViewIndependent()
      .ToElements();
 
    foreach( Element element in collector )
    {
      if( null != element.Category
        && 0 < element.Parameters.Size
        && (element.Category.HasMaterialQuantities
          || null != element.PhaseCreated) )
      {
        if( !categories.ContainsKey(
          element.Category.Name ) )
        {
          categories.Add(
            element.Category.Name,
            element.Category );
        }
        elements.Add( element );
      }
    }
  }
}

If in future there will be any need to retrieve some specific categories, I can use a list of specific categories in addition to this approach.

For now I do not know how to eliminate the try catch when needing to check the Location property, because I had situations when an element had a Location, but trying to access it threw an exception.
At the moment I can avoid checking that property completely, though.

I think it is good idea to share :) maybe someone like me will have a similar problem also :)

Answer: The call to ToElements in this code is completely redundant, as Scott explains in his

update notes
,
so it should simply be removed.

Response: Interesting things. I will try to improve them.
The HasMaterialQuantities property is not set in every element I am interested in, just as Scott said.
This is why I used the Location property to populate the dictionary with all specific elements :)


Comments

4 responses to “Selecting Model Elements”

  1. Simon Tsang Avatar
    Simon Tsang

    Thanks for this great topic. After the implementation of the above code at least I get close to what I want.
    What I would like to know is during IFC export in Revit, how does the export routine tell which element required to export?
    Many Thanks

  2. frank halliday Avatar
    frank halliday

    Hi Jeremy,
    I am new to working with the revit API to create addins, so I hope this is framed correctly.
    I have a filtered element collector and I use this to create an array,
    IList cols = ColumnCollector.WherePasses(filter).WhereElementIsNotElementType().ToElements();
    i retrieve the built in category properties and within the same for each loop I want to also retrieve the family symbol for each element. Is it possible to cast or do something else to aquire both an elements built in property and its family symbol so that i can create a new family with this information?
    cheers,
    fabs

  3. Dear Fabs,
    You submit a rather mixed up question. Hmm. I would say yes. You are already retrieving the built-in category. Fine. Of course you can also retrieve the family symbol, but obviosuly only for family instances. So you would have to cast the element to a family instance. If that fails, it has no symbol, else you can query it using the FamilyInstance.Symbol property. Now it gets really hairy. I assume when you say “create a family” you mean something utterly different, e.g. “place a family instance”. If so, then yes, possibly. But now my answer is too long and hypothetical anyway.
    Cheers, Jeremy.

  4. frank halliday Avatar
    frank halliday

    Got it, thanx Jeremy I didn’t know about the symbol property sitting right under my nose.
    this is a fragment of what i used.
    //var
    FamilySymbol familySymbol = null;
    ElementMulticategoryFilter filter = new ElementMulticategoryFilter(BuiltInCategoryType);
    // Apply the filter to the elements in the active document
    // Use shortcut WhereElementIsNotElementType() to find col instances only
    FilteredElementCollector ColumnCollector = new FilteredElementCollector(doc);
    IList cols = ColumnCollector.WherePasses(filter).WhereElementIsNotElementType().ToElements();
    prompt = “The cols in the current document are:\n”;
    foreach (Element e in cols)
    {
    //get the family symbol from the family instance
    FamilyInstance col = e as FamilyInstance;
    familySymbol = col.Symbol;
    }

Leave a Reply

Discover more from Autodesk Developer Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading