Revit Level change with dynamo script

I open a Revit level in a project to select all elements of a category. I have a Dynamo script that selects all elements of a category via the Data-Shapes pakete ListView.

I now want to switch levels without closing the Dynamo script, so that I can select the desired elements here as well, which will then all be displayed in the ListView. Can I do this without using Python nodes, or do I need them?

Thank you for your help.

Timo

Elemente_auswaehlen_view_Form1.dyn (35.2 KB)

Why not make your script in such a way so you can pick the Levels too with like a tick box?

That’s exactly what I want to do, but I can’t figure out which node to use for it. Would the ListView node be sufficient?

Yes. ListView from Data-Shapes gives you a selection box for each item in your ListView. You just feed in the level name as the keys and the level objects themselves as the values. That should return the list of level elements you select in the UI.

A screenshot of your graph in action might be helpful.

Here a pic to selecte the level an selecte the windows, after this we want to change to the next level and selecte the windos again on this level and work with them.

So you want to run the graph a second time?

Why not just use player and do that if so?

Gotcha. So you’re not automatically selecting all windows from the selected levels. You want to select a Level from the UI, list out the available Windows from that Level in another UI selection, select the Windows you want to include, and then repeat the process for another Level. Is this because you need to execute logic for the entire selection as a group or is this just to make it faster for automating multiple levels one by one? If you just want to do one level after another, just run your script in Dynamo Player - execute one level, then execute again with a different level.

If you need to manage multiple levels at once, you’ll want to rethink your UI logic. Rather than looping through Level and Window selections independently, just select from everything all at once. Select from a ListView of all available Levels, then collect and list all Windows from the selected Levels in a new ListView. This way you only execute the UI logic once, but can select from multiple Levels at the same time.

I would do it like this tbh (select the windows on selected Levels),
but maybe your worklow doesn’t allow for that(?).

Sounds like the workflow becomes “all elements of category” windows.

Then “select levels” with the Datashapes UI nodes.

Then filter by bool mask to remove windows which are not on the selected level.

Hi,

Thank you for your suggestion; it sounds very good. I’ll give it a go. I’ll work through it step by step in the Player.

I’ll start with one level at a time, select only specific windows, and add the parameters with their values until I’ve applied the parameters to all the windows in that level.

Then I’ll select the next level in the Player and add the parameters again.

So, without using the level selection in the ListView.

You still could select Windows on selected Levels only using Data-Shapes though (if you want to).
So you can do it all in one go.
Not sure were you are stuck using this approach.

Dear Bas,

Thank you very much for your message.

I’m quite new to Dynamo programming and I’m stuck here, so I think this would be the easier way to start with.

Perhaps you could send me a small example of how I can implement this using List.View.

You’ll find my Dynamo script, which I’ve already created, available for download above.

When i have time i’ll send you a small example.

When you have it working for just Dynamo or the Dynamo player it is a very small
step to Data-Shapes. Data-Shapes is mostly for making the dialogue windows and such.

I’d go with this, selecting a new level would update the filtered collection.

Player friendly and all nodes are out of the box tools.

Thank you very much, I’ve recreated it. I’m getting errors and it’s not running smoothly. I can’t find the accounts you used for element.ID in my packages.

Document.ActiveView also returns an error for me.
And the second element.ID nodes return null as the return value.

Finally, I have an empty list in the Watch node.

The coincidence

It looks like you’re using a custom node and not the out of the box Document.Current, which returns a different object type / different wrapper.

This thread goes into it a little bit more and shows you how to convert between the two with Rhythm nodes.

This is almost certainly the issue.

Element.Id should also be VERY easy to find. Search it directly in the library and look at the location int he library (click the underlined text) to confirm you’re getting the out of the box tool.

I`m working with the Revit 2024 version.

Many thanks.

Hi,

just for the challenge and fun

here is a solution with WPF + MVVM (with Revit 2025-2026 + PythonNet3),

import clr
import sys
import System
from System import Array
from System.Collections.Generic import List, IList, Dictionary
from System.Collections.ObjectModel import ObservableCollection
#import Revit API
clr.AddReference('RevitAPI')
import Autodesk
from Autodesk.Revit.DB import *
import Autodesk.Revit.DB as DB

clr.AddReference('RevitAPIUI')
import Autodesk.Revit.UI as RUI
from Autodesk.Revit.UI import *
from Autodesk.Revit.UI.Selection import *

clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

#Get Important vars
doc = DocumentManager.Instance.CurrentDBDocument
uidoc = DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument


clr.AddReference("System.Core")
clr.ImportExtensions(System.Linq)

clr.AddReference("System.Xml")
clr.AddReference("PresentationFramework")
clr.AddReference("System.Xml")
clr.AddReference("PresentationCore")
clr.AddReference("System.Windows")
import System.Windows.Controls 
from System.Windows.Controls import *
from System.IO import StringReader
from System.Xml import XmlReader
from System.Windows import LogicalTreeHelper 
from System.Windows.Markup import XamlReader, XamlWriter
from System.Windows import Window, Application
from System.ComponentModel import INotifyPropertyChanged, PropertyChangedEventArgs
from System.ComponentModel import INotifyPropertyChanged, PropertyChangedEventArgs, ICollectionView
from System.Windows.Data import CollectionViewSource, CollectionView

import time
import traceback
import itertools

def get_LevelId(item):
    val = None
    if hasattr(item, "LevelId"): 
         return item.LevelId
    if hasattr(item, "Level"):
        val = item.Level
        if val: return val.Id
    if hasattr(item, "GenLevel"):
        val = item.GenLevel
        if val: return val.Id
    if not val:
        try: return item.get_Parameter(BuiltInParameter.INSTANCE_REFERENCE_LEVEL_PARAM).AsElementId()
        except: 
            try: return item.get_Parameter(BuiltInParameter.INSTANCE_SCHEDULE_ONLY_LEVEL_PARAM).AsElementId()
            except: return ElementId.InvalidElementId

class ViewModel3(INotifyPropertyChanged): 
    __namespace__ = "ViewModel3_jAGfZcdpD" # rename it each edition class
    def __init__(self, lst_elems, lst_lvl):
        super().__init__()
        self._lst_lvl = List[DB.Level]([x for x in lst_lvl])
        self._SelectLvl = None 
        #
        self._lst_elem = ObservableCollection[DB.Element](lst_elems)
        # Create collection view for filtering
        self._filteredItems = CollectionViewSource.GetDefaultView(self._lst_elem)
        self._filteredItems.Filter = System.Predicate[System.Object](self.FilterItems)
        #
        self._property_changed_handlers = []
        
    @clr.clrproperty(List[DB.Level])
    def Levels(self):
        return self._lst_lvl
    
    def get_SelectLvl(self):
        return self._SelectLvl
    def set_SelectLvl(self, value):
        try:
            self._SelectLvl = value
            self.OnPropertyChanged("SelectLvl")
            self._filteredItems.Refresh()
        except Exception as ex:
            print(traceback.format_exc())
    # Add SelectValue as a clr property
    SelectLvl = clr.clrproperty(DB.Level, get_SelectLvl, set_SelectLvl)
       
    @clr.clrproperty(ICollectionView)
    def LstItems(self):
        return self._filteredItems
            
    def FilterItems(self, item):
        """Filters ComboBox items based on SearchText"""
        try:
            item_lvlId = get_LevelId(item)
            if self._SelectLvl is not None:
                return  item_lvlId == self._SelectLvl.Id
            else:
                return True
        except Exception as ex:
            print(traceback.format_exc())
            return True
            
    def OnPropertyChanged(self, property_name):
        event_args = PropertyChangedEventArgs(property_name)
        for handler in self._property_changed_handlers:
            handler(self, event_args)

    # Implementation of add/remove_PropertyChanged
    def add_PropertyChanged(self, handler):
        if handler not in self._property_changed_handlers:
            self._property_changed_handlers.append(handler)

    def remove_PropertyChanged(self, handler):
        if handler in self._property_changed_handlers:
            self._property_changed_handlers.remove(handler)


class MainWindow(Window):
    string_xaml = '''
        <Window 
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                Title="Select Items" Width="520" Height="520"
                WindowStartupLocation="CenterScreen">
            <Grid Margin="10">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="*" />
                    <RowDefinition Height="30" />
                </Grid.RowDefinitions>
        
                <GroupBox Header="Select Level" Grid.Row="0" Margin="0,0,0,8">
                    <ComboBox x:Name="cbLvl" MinWidth="240" Margin="8"
                            ItemsSource="{Binding Levels}"
                            DisplayMemberPath="Name"
                            SelectedItem="{Binding SelectLvl, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
                </GroupBox>
                
                <ListView Name="checkedListBox1" 
                    HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                    SelectionMode="Extended" 
                    ItemsSource="{Binding LstItems}" 
                    Grid.Row="1" Margin="5,5,5,5">
                    <ListView.ItemTemplate>
                        <DataTemplate>
                        <StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                            <CheckBox Name="familyCheck"  VerticalAlignment="Center"  Margin="0,0,0,0" 
                                IsChecked="{Binding Path=IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}}">
                                <CheckBox.Content>
                                        <TextBlock>
                                            <Run Text="Id : " />
                                            <Run Text="{Binding Id.Value, Mode=OneWay, StringFormat=\{0\}}" />
                                            <Run Text="-" />
                                            <Run Text="{Binding Name}" />
                                        </TextBlock>
                                </CheckBox.Content>
                            </CheckBox>
                        </StackPanel>
                        </DataTemplate>
                    </ListView.ItemTemplate>
                </ListView>
                <Button x:Name="btnSelect" Grid.Row="2" Content="Select Checked Items" HorizontalAlignment="Right" Width="180" />
            </Grid>
        </Window>'''
  
    def __init__(self,lst_Elems):
        super().__init__()
        self._lst_elems = lst_Elems
        self._lst_lvls = FilteredElementCollector(doc).OfClass(DB.Level).WhereElementIsNotElementType()\
                            .OrderBy[DB.Element, System.Double](System.Func[DB.Element, System.Double](lambda x :  x.ProjectElevation))\
                            .ToList()
        #
        xr = XmlReader.Create(StringReader(MainWindow.string_xaml))
        self.winLoad = XamlReader.Load(xr) 
        #
        self.vm = ViewModel3( self._lst_elems, self._lst_lvls)
        self.InitializeComponent()
        
    def InitializeComponent(self):
        try:
            self.winLoad.DataContext = self.vm
            #
            self.checkedListBox1 = LogicalTreeHelper.FindLogicalNode(self.winLoad, "checkedListBox1")
            self.btnSelect = LogicalTreeHelper.FindLogicalNode(self.winLoad, "btnSelect")
            self.btnSelect.Click += self.ButtonOKClick
            #
        except Exception as ex:
            print(traceback.format_exc())
              
        
    def ButtonOKClick(self, sender, e):
        try:
            selectIds = List[ElementId]([x.Id for x in self.checkedListBox1.SelectedItems])
            uidoc.Selection.SetElementIds(selectIds)
        except Exception as ex:
            print(traceback.format_exc())
            
            
lst_Elems = List[DB.Element](UnwrapElement(IN[0]))
lst_Elems = lst_Elems.OrderBy[DB.Element, System.String](System.Func[DB.Element, System.String](lambda x : x.Name)).ToList()
objWindow = MainWindow(lst_Elems)
objWindow.winLoad.Show()

OUT = 0

This solution will probably seem complicated to beginners