Revit API Forum
Welcome to Autodesk’s Revit API Forums. Share your knowledge, ask questions, and explore popular Revit API topics.
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Select all content on level

14 REPLIES 14
SOLVED
Reply
Message 1 of 15
PerryLackowski
4418 Views, 14 Replies

Select all content on level

I'm trying to put together a new script for one of our structural engineers. He wants to be able to quickly see all the elements that are linked to a particular Level of his choosing. I chose to break this into two parts:

  1. Take the first element in his selection and retrieve its Level.
  2. Find all elements with that reference Level and update the selection to match.

Here's my current code:

from Autodesk.Revit.DB import ElementLevelFilter, FilteredElementCollector
from Autodesk.Revit.DB import Document
from rpw import ui

doc = __revit__.ActiveUIDocument.Document

selection = ui.Selection()

if selection:
    
    if str(selection[0].LevelId) != "-1":
    # if selection[0].LevelId is not None: # seems like this should work, but it doesn't, so I had to convert to strings
        print "LevelId found: " + str(selection[0].LevelId)
        level_id = selection[0].LevelId
    else:
        if selection[0].ReferenceLevel:
            print "ReferenceLevel found:" + selection[0].ReferenceLevel.Name + str(selection[0].ReferenceLevel.Id)
            level_id = selection[0].ReferenceLevel.Id
        else:
            print "nothing found..."
            level_id = None
    
    if level_id:
        level_filter = ElementLevelFilter(level_id)
        
        elements_on_level = FilteredElementCollector(doc).WherePasses(level_filter).ToElements()
        
        if elements_on_level:
            #print "Found elements on level."
            
            # for elem in elements_on_level:
                # print elem.Name
            
            selection.clear()
            selection.add(elements_on_level)
            selection.update()
            
        # else:
            # print "Did not find any elements."
            
    # except:
        # print "Something went wrong."
        # pass
        
# else:
    # print "nothing selected"

Step 1 was more challenging that I realized because some elements like cable trays store the associated Level in the ReferenceLevel instead of in the LevelId. If you try to retrieve their LevelId, -1 is returned.

Step 2 is where I'm struggling now. I use a FilteredElementCollector to find all matching LevelIds, but similar to step 1, this fails to include the cable trays in the resulting selection because they have a LevelId of -1.

I'm hoping I'm missing something obvious because I haven't spent enough time with API tutorials, but I'd appreciate if someone could point me in the right direction. Thanks!

14 REPLIES 14
Message 2 of 15

Your explanation makes perfect sense, and also points towards the solution.

 

Just as you noticed in retrieving the level from the selected element, different elements store their level in different ways. Unfortunately, some do not store any level information directly at all. Those could be retrieved by determining their Z elevation and comparing that with the various level's Z coordinates.

 

Many elements provide a valid LevelId property, and you have used that property to retrieve the level from the selected element.

 

The cable trays apparently do not, and you have to use the ReferenceLevel property instead.

 

I assume that the ElementLevelFilter is also based on the LevelId property. Therefore, it will not retrieve the cable trays. For those, you can implement a second, separate, filtered element collector that first filters for cable trays, e.g., using their category or some other quick filter property. In a post-processing step, you could check that the value of their ReferenceLevel property matches the desired value. 

 

These two separate filtered element collectors can be combined into one using a Boolean operation.

  

I used this technique to put together such combinations of filters to retrieve structural elements, MEP elements and their connectors:

 

 

You can check out The Building Coder topic group on filtering for elements to see many more examples:

 

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
Message 3 of 15

Thanks Jeremy!

 

I'm still reading through the pages on your site - those are helpful links!

 

I didn't realize there was so much diversity in the way Revit handles the different type categories behind the scenes until I downloaded your Revit Lookup snoop tool yesterday. For example, I see that Duct and Cable Tray are broken out into separate Duct and CableTray Objects, and they both use Reference Levels. But Duct Fittings and Cable Tray Fittings are saved under the same FamilyInstance Object and they both use LevelIds. Is there a guide/roadmap for how different type categories map to different database objects? It sounds like I need to find out how each and every type category handles levels behind the scenes, then set up different filters to separate them ...which could be very time consuming.

 

Also, (and perhaps this would be a better question to ask on the pyRevit forums,) is there a better way to check for a null value on the LevelId parameter? I feel like converting the result to a string is not the right solution here. 

Message 4 of 15

I'm beginning to wrap my head around the complexity of this problem. From the items I have snooped so far, here are my results:

  • Light Fixtures use Level (LevelId behind the scenes), unless they are face based, then they use Schedule Level (not in snoop tool, and LevelId is null).
  • Cable Tray and Duct use Reference Level (Does appear in snoop tool, LevelId is null). 
  • Model Groups use Reference Level (LevelId behind the scenes)
  • Structural Columns use Base Level (LevelId behind the scenes)
  • Structural Foundations use Level (LevelId behind the scenes)
  • Structural Framing has a Reference Level (not in snoop tool, and LevelId is null).

Clearly there's not a lot of consistency behind the scenes. I figured I'd take a look at the parameters next, and maybe filter based on those. Using the RevitAPI doc for BuiltInParameter Enumeration, I'm seeing a lot of potential parameters that could house the level:

 

Reference Level corresponds to MULTISTORY_STAIRS_REF_LEVEL, FABRICATION_LEVEL_PARAM, TRUSS_ELEMENT_REFERENCE_LEVEL_PARAM, GROUP_LEVEL, SPACE_REFERENCE_LEVEL_PARAM, RBS_START_LEVEL_PARAM, FACEROOF_LEVEL_PARAM, STRUCTURAL_REFERENCE_LEVEL_ELEVATION, ROOF_CONSTRAINT_LEVEL_PARAM, INSTANCE_REFERENCE_LEVEL_PARAM

Base Level corresponds to DPART_BASE_LEVEL_BY_ORIGINAL, DPART_BASE_LEVEL, STAIRS_BASE_LEVEL, STAIRS_RAILING_BASE_LEVEL_PARAM, IMPORT_BASE_LEVEL, STAIRS_BASE_LEVEL_PARAM, VIEW_UNDERLAY_BOTTOM_ID, SCHEDULE_BASE_LEVEL_PARAM, ROOF_BASE_LEVEL_PARAM, FAMILY_BASE_LEVEL_PARAM

Schedule Level corresponds to INSTANCE_SCHEDULE_ONLY_LEVEL_PARAM

Level corresponds to PATH_OF_TRAVEL_LEVEL_NAME, SYSTEM_ZONE_LEVEL_ID, ZONE_LEVEL_ID, WALL_SWEEP_LEVEL_PARAM, ROOM_LEVEL_ID, SLOPE_ARROW_LEVEL_END, CURVE_LEVEL, VIEW_GRAPH_SCHED_BOTTOM_LEVEL, SCHEDULE_LEVEL_PARAM, LEVEL_PARAM, STRUCTURAL_REFERENCE_LEVEL_ELEVATION, STRUCTURAL_ATTACHMENT_START_LEVEL_REFERENCE, FAMILY_LEVEL_PARAM

Associated Level corresponds to PLAN_VIEW_LEVEL

 

Admittedly, a lot of these built-in parameters sound like they correspond to families that I'm not looking for, but there are certainly quite a few contenders that may contain the information I need. How would I filter for these? I'm thinking I can create a list of all the parameters, then check for the parameters on each element and if I find a non-null parameter I can compare it to my selected level. But this would be a very slow filter. And how would I get a list of all the elements in the first place?

Message 5 of 15

Ok so I think I figured out the first part with this new method. Basically it just searches for every parameter in the list, and if it finds one that doesn't equal -1, it will return it as the Element Id of the corresponding level. Next I need to find a way to run this on every element in the project so I can compare the element ids. This feels like the slowest, most brute-force way to accomplish this, so I'm open to alternatives.

 

from Autodesk.Revit.DB import ElementLevelFilter, FilteredElementCollector
from Autodesk.Revit.DB import Document, BuiltInParameter
doc = __revit__.ActiveUIDocument.Document
uidoc = __revit__.ActiveUIDocument

#Ask user to pick an object which has the desired reference level
def get_first_element_or_selection():
    from rpw import ui
    selection = ui.Selection()
    
    if selection:
        return selection[0]
    else:
        from Autodesk.Revit.UI.Selection import ObjectType
        return doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element, "Select an element.").ElementId)

selected_element = get_first_element_or_selection()
print "Element selected: " + selected_element.Name

BIPs = [
    BuiltInParameter.MULTISTORY_STAIRS_REF_LEVEL,
    #BuiltInParameter.FABRICATION_LEVEL_PARAM,
    BuiltInParameter.TRUSS_ELEMENT_REFERENCE_LEVEL_PARAM,
    #BuiltInParameter.GROUP_LEVEL,
    #BuiltInParameter.SPACE_REFERENCE_LEVEL_PARAM,
    #BuiltInParameter.RBS_START_LEVEL_PARAM,
    BuiltInParameter.FACEROOF_LEVEL_PARAM,
    BuiltInParameter.STRUCTURAL_REFERENCE_LEVEL_ELEVATION,
    BuiltInParameter.ROOF_CONSTRAINT_LEVEL_PARAM,
    BuiltInParameter.INSTANCE_REFERENCE_LEVEL_PARAM,
    BuiltInParameter.DPART_BASE_LEVEL_BY_ORIGINAL,
    BuiltInParameter.DPART_BASE_LEVEL,
    BuiltInParameter.STAIRS_BASE_LEVEL,
    BuiltInParameter.STAIRS_RAILING_BASE_LEVEL_PARAM,
    BuiltInParameter.IMPORT_BASE_LEVEL,
    BuiltInParameter.STAIRS_BASE_LEVEL_PARAM,
    BuiltInParameter.VIEW_UNDERLAY_BOTTOM_ID,
    BuiltInParameter.SCHEDULE_BASE_LEVEL_PARAM,
    BuiltInParameter.ROOF_BASE_LEVEL_PARAM,
    BuiltInParameter.FAMILY_BASE_LEVEL_PARAM,
    BuiltInParameter.INSTANCE_SCHEDULE_ONLY_LEVEL_PARAM,
    BuiltInParameter.PATH_OF_TRAVEL_LEVEL_NAME,
    BuiltInParameter.SYSTEM_ZONE_LEVEL_ID,
    #BuiltInParameter.ZONE_LEVEL_ID,
    BuiltInParameter.WALL_SWEEP_LEVEL_PARAM,
    BuiltInParameter.ROOM_LEVEL_ID,
    BuiltInParameter.SLOPE_ARROW_LEVEL_END,
    BuiltInParameter.CURVE_LEVEL,
    BuiltInParameter.VIEW_GRAPH_SCHED_BOTTOM_LEVEL,
    BuiltInParameter.SCHEDULE_LEVEL_PARAM,
    BuiltInParameter.LEVEL_PARAM,
    BuiltInParameter.STRUCTURAL_REFERENCE_LEVEL_ELEVATION,
    BuiltInParameter.FAMILY_LEVEL_PARAM,
    BuiltInParameter.PLAN_VIEW_LEVEL
]

def get_level_id(elem):
    for BIP in BIPs:
        param = elem.get_Parameter(BIP)
        if param:
            elem_id = param.AsElementId()
            if str(elem_id) != "-1":
                #print BIP
                #print elem_id
                return elem_id

print get_level_id(selected_element)

 

Message 6 of 15

Latest version, basically complete!

 

I discovered that some levels can't be retrieved through the get_Parameter method - they only appear in the .LevelId and .ReferenceLevel methods. But these methods don't exist for every element type, so I wrapped them in some Try/Except statements at the end of the level retrieval function.

 

I discovered a solution to the -1 issue as well. If you retrieve the Element ID and it's null, it means that level parameter doesn't exist for that object. But if you retrieve an Element ID that equals -1, that means the parameter exists, but was never set. I believe the correct way to check for this is by comparing the Element ID to ElementId.InvalidElementId, like so:

 

level_id.Compare(ElementId.InvalidElementId) == 1:

 

I also added some options so you can select the starting element before or after launching the script.

 

Unfortunately, I was forced to make a list of all the categories I want to search through, since I haven't found an easier way to filter down the FilteredElementCollector. I included maybe 30 of the greater than 1000 categories, but it's not an exhaustive list, and there's a possibility I missed a few important ones that I'll discover later. I wish this page separated the 3D model categories from the rest, but alas.

 

 

"""
Selects all elements that share the same Reference Level as the selected element.

TESTED REVIT API: 2020.2.4

Author: Robert Perry Lackowski

"""

from Autodesk.Revit.DB import ElementLevelFilter, FilteredElementCollector
from Autodesk.Revit.DB import Document, BuiltInParameter, BuiltInCategory, ElementFilter, ElementCategoryFilter, LogicalOrFilter, ElementIsElementTypeFilter, ElementId
from Autodesk.Revit.Exceptions import OperationCanceledException
# from pyrevit import DB
doc = __revit__.ActiveUIDocument.Document
uidoc = __revit__.ActiveUIDocument

from rpw import ui
import sys

#Ask user to pick an object which has the desired reference level
def pick_object():
    from Autodesk.Revit.UI.Selection import ObjectType
    
    try:
        picked_object = uidoc.Selection.PickObject(ObjectType.Element, "Select an element.")
        if picked_object:
            return doc.GetElement(picked_object.ElementId)
        else:
            sys.exit()
    except:
        sys.exit()
    
def get_level_id(elem):
    
    BIPs = [
        BuiltInParameter.CURVE_LEVEL,
        BuiltInParameter.DPART_BASE_LEVEL_BY_ORIGINAL,
        BuiltInParameter.DPART_BASE_LEVEL,
        # BuiltInParameter.FABRICATION_LEVEL_PARAM,
        BuiltInParameter.FACEROOF_LEVEL_PARAM,
        BuiltInParameter.FAMILY_BASE_LEVEL_PARAM,
        BuiltInParameter.FAMILY_LEVEL_PARAM,
        BuiltInParameter.GROUP_LEVEL,
        BuiltInParameter.IMPORT_BASE_LEVEL,
        BuiltInParameter.INSTANCE_REFERENCE_LEVEL_PARAM,
        BuiltInParameter.INSTANCE_SCHEDULE_ONLY_LEVEL_PARAM,
        BuiltInParameter.LEVEL_PARAM,
        BuiltInParameter.MULTISTORY_STAIRS_REF_LEVEL,
        BuiltInParameter.PATH_OF_TRAVEL_LEVEL_NAME,
        BuiltInParameter.PLAN_VIEW_LEVEL,
        # BuiltInParameter.RBS_START_LEVEL_PARAM,
        BuiltInParameter.ROOF_BASE_LEVEL_PARAM,
        BuiltInParameter.ROOF_CONSTRAINT_LEVEL_PARAM,
        BuiltInParameter.ROOM_LEVEL_ID,
        BuiltInParameter.SCHEDULE_BASE_LEVEL_PARAM,
        BuiltInParameter.SCHEDULE_LEVEL_PARAM,
        BuiltInParameter.SLOPE_ARROW_LEVEL_END,
        # BuiltInParameter.SPACE_REFERENCE_LEVEL_PARAM,
        BuiltInParameter.STAIRS_BASE_LEVEL,
        BuiltInParameter.STAIRS_BASE_LEVEL_PARAM,
        BuiltInParameter.STAIRS_RAILING_BASE_LEVEL_PARAM,
        BuiltInParameter.STRUCTURAL_REFERENCE_LEVEL_ELEVATION,
        BuiltInParameter.SYSTEM_ZONE_LEVEL_ID,
        BuiltInParameter.TRUSS_ELEMENT_REFERENCE_LEVEL_PARAM,
        BuiltInParameter.VIEW_GRAPH_SCHED_BOTTOM_LEVEL,
        BuiltInParameter.VIEW_UNDERLAY_BOTTOM_ID,
        BuiltInParameter.WALL_BASE_CONSTRAINT,
        BuiltInParameter.WALL_SWEEP_LEVEL_PARAM
        # BuiltInParameter.ZONE_LEVEL_ID,
    ]
    
    level_id = None
        
    for BIP in BIPs:
        param = elem.get_Parameter(BIP)
        if param:
            # print "A common level parameter has been found:" + str(BIP)
            param_elem_id = param.AsElementId()
            if param_elem_id.Compare(ElementId.InvalidElementId) == 1:
                level_id = param_elem_id
                # print "match found on common level parameter " + str(BIP) + "Level ID: " + str(level_id)
                return level_id
    
    # print "No matching common level parameters found, checking for .LevelId"
    try:
        level_id = elem.LevelId
        if level_id.Compare(ElementId.InvalidElementId) == 1:
            # print "match found on .LevelId. Level ID: " + str(level_id)
            return level_id
    except:
        # print "No LevelId parameter on this element."
        pass

    # print "Still no matches. Try checking for .ReferenceLevel.Id"
    
    try:
        level_id = elem.ReferenceLevel.Id
        if level_id.Compare(ElementId.InvalidElementId) == 1:
            # print "match found on .ReferenceLevel.Id Level ID: " + str(level_id)            
            return level_id
    except:
        # print "No ReferenceLevel parameter on this element."
        pass
    
    # print "No matches found. Returning None..."
    return None

# print "get selected element, either from current selection or new selection"
selection = ui.Selection()

if selection:
    selected_element = selection[0]
else:
    selected_element = pick_object()

#print "Element selected: " + selected_element.Name

# print "Search selected element for its reference level's element ID"
target_level_id = get_level_id(selected_element)
# print target_level_id

if target_level_id is not None:
    
    #poor attempts at filtering FECs. Not filtered enough - they contain far too many elements.
    #all_elements = FilteredElementCollector(doc).ToElements()
    #all_elements = FilteredElementCollector(doc).WherePasses(LogicalOrFilter(ElementIsElementTypeFilter( False ), ElementIsElementTypeFilter( True ) ) ).ToElements()
    
    #Create a filter. If this script isn't selecting the elements you want, it's possible the category needs to be added to this list.
    BICs = [
        BuiltInCategory.OST_CableTray,
        BuiltInCategory.OST_CableTrayFitting,
        BuiltInCategory.OST_Conduit,
        BuiltInCategory.OST_ConduitFitting,
        BuiltInCategory.OST_DuctCurves,
        BuiltInCategory.OST_DuctFitting,
        BuiltInCategory.OST_DuctTerminal,
        BuiltInCategory.OST_ElectricalEquipment,
        BuiltInCategory.OST_ElectricalFixtures,
        BuiltInCategory.OST_FloorOpening,
        BuiltInCategory.OST_Floors,
        BuiltInCategory.OST_FloorsDefault,
        BuiltInCategory.OST_LightingDevices,
        BuiltInCategory.OST_LightingFixtures,
        BuiltInCategory.OST_MechanicalEquipment,
        BuiltInCategory.OST_PipeCurves,
        BuiltInCategory.OST_PipeFitting,
        BuiltInCategory.OST_PlumbingFixtures,
        BuiltInCategory.OST_RoofOpening,
        BuiltInCategory.OST_Roofs,
        BuiltInCategory.OST_RoofsDefault,
        BuiltInCategory.OST_SpecialityEquipment,
        BuiltInCategory.OST_Sprinklers,
        BuiltInCategory.OST_StructuralStiffener,
        BuiltInCategory.OST_StructuralTruss,
        BuiltInCategory.OST_StructuralColumns,
        BuiltInCategory.OST_StructuralFraming,
        BuiltInCategory.OST_StructuralFramingSystem,
        BuiltInCategory.OST_StructuralFramingOther,
        BuiltInCategory.OST_StructuralFramingOpening,
        BuiltInCategory.OST_StructuralFoundation,
        BuiltInCategory.OST_Walls,
        BuiltInCategory.OST_Wire,
    ]
    
    category_filters = []
    
    for BIC in BICs:
        category_filters.Add(ElementCategoryFilter(BIC))
    
    final_filter = LogicalOrFilter(category_filters)
    
    #Apply filter to create list of elements
    all_elements = FilteredElementCollector(doc).WherePasses(final_filter).WhereElementIsNotElementType().WhereElementIsViewIndependent().ToElements()

    # print "Number of elements that passed collector filters:" + str(len(all_elements))

    selection.clear()

    for elem in all_elements:
        elem_level_id = get_level_id(elem)
        if elem_level_id == target_level_id:
            selection.add(elem)

    selection.update()
    
else:
    
    print "No level associated with element."

 

 

Message 7 of 15

Thank you for all your research and sharing the solution in its current state! Edited and preserved for posterity on the blog:

  

https://thebuildingcoder.typepad.com/blog/2021/09/view-sheet-from-view-and-select-all-on-level.html#... 

 

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
Message 8 of 15

Thanks Jeremy, I hope you and others find this helpful!

 

One little improvement, at the suggestion of our structural engineer. There are times he wants to move a level, and doesn't know what's on the level. So in these situations, he'd like to select the level, rather than something on the level. To implement this, I added an additional if statement, as below:

Replace this:

# print "Search selected element for its reference level's element ID"
target_level_id = get_level_id(selected_element)

With this:

# print "Check if selected element is a Level and get its ID. If not, search through the parameters for the reference level."
if selected_element.Category.Name.Equals("Levels"):
    target_level_id = selected_element.Id
else:
    target_level_id = get_level_id(selected_element)

You may be able to help me create a language agnostic version of this, but I was unable to get the syntax working on my own.

Message 9 of 15

Thank you for the improvement. It looks pretty language agnostic to me... at least the idea is simple and clear and effective.

 

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
Message 10 of 15

... added the improvement to the blog post as well.

 

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
Message 11 of 15

I was referring to spoken languages, rather than programming languages. It
checks against the english string for lines rather than checking against
the Revit class for lines.
Message 12 of 15

Oh, of course. Yes, very good idea. The language agnostic identifier is BuiltInCategory.OST_Levels. It is a negative integer value. You compare it with the element's category Id property, e.g., using 

 

  elem.Category.Id.IntegerValue.Equals( (int) BuiltInCategory.OST_Levels )

   

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
Message 13 of 15

Awesome, thanks!

Message 14 of 15

I've been trying to run this code and it works when I run it on RPS but when I run on pyRevit with CPython, I get the error.

 

TypeError : No method matches given arguments for get_Parameter: (<class 'int'>)
 
This error is at 
param = element.get_Parameter(BIP)
 
When I print the BIP, the O/P is an integer. What could be the potential reason for this? 
 
When I use this line - 
exec("param = %s" % BIP)
 
The code runs but the O/P is an integer and the code errors at param.AsElementId(). 
 
How do I rectify this?
 
However, the code runs when I remove - #!python3. 
So does that mean get_Parameter doesn't work in CPython since I tried printing get_Parameter with a random BuiltInParameter and it showed the same error?
Message 15 of 15

Nice to hear that it runs. For the Python specific aspects, you will probably be better served in the Dynamo forum, since we really only discuss the pure .NET desktop Revit API here:

  

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Autodesk Design & Make Report