Traverse all model elements in a project - top-down approach

Traverse all model elements in a project - top-down approach

Chintan.Shah
Participant Participant
1,916 Views
10 Replies
Message 1 of 11

Traverse all model elements in a project - top-down approach

Chintan.Shah
Participant
Participant

Hello,

 

I am trying to find a way to traverse all the model elements in a Revit project in top down manner:

 

levels -> categories -> families -> family types -> instances

 

Get all levels in current project.

For each level

{

    Get all the categories in current level.

    For each category

    {

        Get all the families in current category

 

        For each family in current category:

        {

             Get all the family types in current family

 

             For each family type in current family:

             {

                        Get all the Instances

             }

        }

    }

}

 

On exploring internet w.r.t getting all model elements in revit, I found following:

http://thebuildingcoder.typepad.com/blog/2009/05/selecting-model-elements.html

 

which is rather a bottom up approach.

 

Is there a way to traverse revit model elements in top down manner?

 

Also, I noticed that using the approach described in above link, the toposurface and entourage elements like RPC car, male, female, etc are missing.

Do these elements require special handling?

 

Thanks & Regards,

Chintan

0 Likes
Accepted solutions (1)
1,917 Views
10 Replies
Replies (10)
Message 2 of 11

Aaron.Lu
Autodesk
Autodesk

there is no way to do that directly
using class filter to get all families, and get category via Family.FamilyCategory
using Family.GetFamilySymbolIds() to get all family types,
using FamilyInstanceFilter to get all family instances.

relationship between level and categories is tricky too (but obvious for Revit developers, you should forget all the stuff learned from AutoCAD etc.)
actually, you are saying: get all the categories of the instances on a specific level. i'm afraid you can only use ElementLevelFilter to get all those instances and see what's the categories of those instances.



Aaron Lu
Developer Technical Services
Autodesk Developer Network
Message 3 of 11

jeremytammik
Autodesk
Autodesk
Accepted solution

Dear Chintan,

 

Thank you for your query.

 

Aaron is absolutely right.

 

Basically, the filtered element collector is the one and only channel to retrieve elements from the Revit database.

 

Before it was 'filtered', it was just an element collector and really slow. Back then, you definitely wanted to retrieve everything of interest in one single call, and then structure it yourself.

 

Now, you could potantially set up numerous filtered element collectors in loops to achieve the kind of hierarchical query you describe.

 

You could start like this:

 

  FilteredElementCollector levels
    = new FilteredElementCollector( doc )
      .OfClass( typeof( Level ) );

  foreach( Level level in levels )
  {
    // Now what?
    // We could set up new filtered element 
    // collectors for each level, but it would
    // get complex and we would start repeating
    // ourselves...
  }

 

I think it would still be better to get all elements at once, and then sort them into the structure yourself, something like this:

 

  // Get all family instances and use those to
  // set up dictionaries for all the required
  // mappings in one fell sweep. In the end, we
  // will need the following mappings:
  // - level to all categories it hosts instances of
  // - for each level and category, all families
  // - family to its types
  // - family type to instances

  FilteredElementCollector instances
    = new FilteredElementCollector( doc )
      .OfClass( typeof( FamilyInstance ) );

  // Top level map.

  Dictionary<ElementId, // level
    List<ElementId>> // categories
      mapLevelToCategories = new
      Dictionary<ElementId,
        List<ElementId>>();

  // What we really need is something like this.
  // It will probably simplify things to implement
  // a custom kind of dictionary for this to add 
  // new entries very simply.

  Dictionary<ElementId, // level
    Dictionary<ElementId, // category
      Dictionary<ElementId, // family
        Dictionary<ElementId, // type
          ElementId>>>> // instance
            map = new Dictionary<ElementId,
              Dictionary<ElementId,
                Dictionary<ElementId,
                  Dictionary<ElementId,
                    ElementId>>>>();

  foreach( FamilyInstance inst in instances )
  {
    Category cat = inst.Category;
    Level lev = doc.GetElement( inst.LevelId ) as Level;
    FamilySymbol sym = inst.Symbol;
    Family fam = sym.Family;

    Debug.Assert( null != cat, "expected valid category" );
    Debug.Assert( null != lev, "expected valid level" );
    Debug.Assert( null != sym, "expected valid symbol" );
    Debug.Assert( null != fam, "expected valid family" );

    if( map.ContainsKey( lev.Id ) )
    {
      mapLevelToCategories[lev.Id].Add( cat.Id );
    }
    else
    {
      // First time we encounter this level, 
      // so start a new level.

      List<ElementId> categoriesOnLevel 
        = new List<ElementId>( 1 );

      categoriesOnLevel.Add( cat.Id );

      mapLevelToCategories.Add( lev.Id, 
        categoriesOnLevel );
    }

    // Sort into families and types pere level and category...
  }

 

I hope this helps.

 

Best regards,

 

Jeremy



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

Message 4 of 11

Chintan.Shah
Participant
Participant

Hi Jeremy,

 

That is precisely what I needed. Thanks!

 

I tried out your suggestion and created a multilevel nested dictionary right from level to family instances.

 

But I noticed that using the FamilyInstance filter, I am not getting those elements which are of System family for example, System Family: Basic Wall.

I also want to query some elements like topography which don't belong to any category/family/level.

Any idea how to get such elements?

 

Thanks a lot!

Chintan

0 Likes
Message 5 of 11

jeremytammik
Autodesk
Autodesk

Dear Chintan,

 

I am very glad that it helps.

 

Thank you for your appreciation.

 

Instances of standard families are represented by FamilyInstance elements.

 

Instances of system families are represented by native object instances such as Wall, Pipe, Duct, etc.

 

You will have to enhance and expand the filtered element collector criteria to include the elements you need.

 

Here is an example to retrieve MEP elements:

 

http://thebuildingcoder.typepad.com/blog/2010/06/retrieve-mep-elements-and-connectors.html

 

By the way, I added the filter that I sketched out for you to The Building Coder samples:

 

https://github.com/jeremytammik/the_building_coder_samples/blob/master/BuildingCoder/BuildingCoder/C...

 

  #region Traverse all model elements top down Levels > Category > Family > Type > Instance

 

You might want to share your version of this once you are happy with it and I will put that in instead.

 

Thank you!

 

Cheers,

 

Jeremy

 



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

Message 6 of 11

Chintan.Shah
Participant
Participant

Hi Jeremy,

 

Thanks for a quick reply!!

 

I went thru the link you pointed to.

It looks like we need to have the list of system families whose instances we want. (to create an ElementClassFilter corresponding to each system family)

 

But I am writing a general addin which should work for any revit project.

So, basically I won't be knowing the list of system families before hand.

 

Is there any general way to get the instances of all the system families in current project?

 

Thanks,

Chintan

0 Likes
Message 7 of 11

Dale.Bartlett
Collaborator
Collaborator

Hi Chintan, I also have the same need. I thought it would be simple to return every model object instance (system or component), however I am still to arrive at a satisfactory final solution. An old post of Jeremy's proposed:

  • Start temporary transaction
  • Create a default 3D view
  • Create FilteredElementCollection on view elements.
  • Apply WhereElementIsNotElementType
  • Iteration
  • Rollback to remove created view
  • Since it seems that 3D elements are what is wanted here, all elements visible in the new (unfiltered) 3D view are targets.

This seems a rather convoluted approach that I would have thought replaced by some FilteredElementCollector code. Did you resolve this as I am stuck? Maybe we need to get all categories in the project, and then iterate that list to look for instances. I must be down rabbit hole... Dale

 




______________
Yes, I'm Satoshi.
0 Likes
Message 8 of 11

jeremytammik
Autodesk
Autodesk

Dear Dale,

 

Yes, there are simpler approaches, that just require executing a single filtered element collector.

 

It all depends on what exactly you are after.

 

Look here for a number of different suggestions and approaches:

 

http://thebuildingcoder.typepad.com/blog/about-the-author.html#5.9

 

I hope one of them suits you needs.

 

How exactly do you define 'model elements'?

 

Cheers,

 

Jeremy



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

Message 9 of 11

Dale.Bartlett
Collaborator
Collaborator

Hi Jeremy, Thanks for the reply. To be clear, "all model elements" could be defined as all objects that exist in the real world (chairs, doors ,walls, etc - component and system families). Graphic elements such as levels, sheets, views, dimensions, annotation, profiles, etc would not be included. I have been through all of your samples (and others) many times, and whilst there are lots of examples to filter for a specitic category or feature, I have been unable to come up with a solution that provides all model elements as defined above. Your example from 2009 may be the best, but given that it is several years old, I have been looking for more contemporary code. Apologies if I am overlooking something obvious; I thought this would be a five minute job but I have been unable to come up with a simple, solid solution. Thanks again, Dale.  

<code>

In 2009, we used the following helper method to select all model elements:

public static ElementSet GetAllModelElements(Application app )
{
  ElementSet elems = app.Create.NewElementSet();
  Geo.Options opt = app.Create.NewGeometryOptions();
  ElementIterator iter = app.ActiveDocument.Elements;
  while( iter.MoveNext() )
  {
    Element e = iter.Current as Element;

    if( !( e is Symbol || e is FamilyBase ) )
    {
      if( null != e.Category )
      {
        if( null != e.get_Geometry( opt ) )
        {
          elems.Insert( e );
        }
      }
    }
  }
  return elems;
}

</code>




______________
Yes, I'm Satoshi.
0 Likes
Message 10 of 11

jeremytammik
Autodesk
Autodesk

Dear Dale,

 

Does this fit the bill?

 

  IEnumerable<Element> GetAllModelElements( 
    Document doc )
  {
    Options opt = new Options();

    return new FilteredElementCollector( doc )
      .WhereElementIsNotElementType()
      .WhereElementIsViewIndependent()
      .Where<Element>( e 
        => null != e.Category
        && null != e.get_Geometry( opt ) );
  }

 

I just now added and published this in The Building Coder samples release 2016.0.120.14:

 

https://github.com/jeremytammik/the_building_coder_samples/releases/tag/2016.0.120.14

 

Please try it out, and let's ensure that we really get something up and running that suits your needs.

 

Cheers,

 

Jeremy



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

Message 11 of 11

Dale.Bartlett
Collaborator
Collaborator

If I ever get to AU again, I owe you a beer! Two maybe. Works perfectly. Thanks again, I hope this is helpful for others as I really struggled to resolve it. Dale




______________
Yes, I'm Satoshi.
0 Likes