Module 10

[Clean Project by Form]

I’m sure we have all been in the situation with Revit where a project becomes so bloated that it struggles to get out of bed in the morning. This can happen for a lot of reasons, and there are plenty of ways that projects can be cleaned up through purging and management. This script is useful to help with management of bloated projects by deleting some of the elements that build up and bog it down. Specifically , it will find elevation markers that are no longer being used, reference lines that are unnamed and don’t host elements, and views that are not placed or needed.

The script works by collecting the elements we want to clean, filtering them based on specific parameters and them sending them through a Windows Form Python script to double check we want to clean them. The reason I have done this is to put in a bit of padding around the Revit API Delete command. It is always very risky putting a delete method in any script so I’ve set up this check to make the user is 100% sure of what they are about to delete.

There is plenty of opportunity to adapt the script to collect and clean up other elements that fit your needs so please feel free to download the file at the bottom of the page and give it a whirl. Also, one bug I have found is if you try to delete the view that your project file is currently on, it will fail, understandably. Please let me know if you find any others!


We begin by gathering up all the elements we want to clean by filtering through the Element Categories and Types. Elevation Markers are the first to get collected so here we use the Categories node to select Elevations and retrieve all the project elevation markers with All Elements of Category. The Watch node is then used to check we have all the elevation markers and these are fed into a Python Script. The Python Script is used to check which of the markers are no longer hosting views and these are output. A more detailed look into the Python Script is explained in part 1.1.


The next group of elements to be collected are reference planes that are unnamed. If you’re not in the game of naming reference planes in large projects, you may not want to include this. This script assumes any reference planes that are needed, will be named. All the reference planes in the project are collected in the same manner as part 1; Categories node is used to select  Reference Planes category which allows us to get all elements in this category with All Elements of Category. The Python Script in this case is used to filter all reference planes that have no name, the details of this are explained in part 2.1.

learn dynamo 3 three
In order to clean up all views in the project that are unplaced, we need to collect them up and test whether each one can be placed on a sheet. If the view cannot be placed on a sheet, it must already be on a sheet and therefore it’s a keeper. So, we can start this process by gathering all sheets with the Categories and All Elements of Category. These sheets are then input into a Python Script which will filter out all unplaced views. The details of this are shown in part 3.1. 
All the unplaced views are output and shown in the Watch node so we can keep track of what views we are dealing with. To keep it simple, I have only filtered Floor Plans, Ceiling Plans, Sections, Elevations and Perspectives however, feel free to change this as needed. The Sheet.SheetNumber node is only for checking, it ensures that we are inputting a sheet that views can be placed on.
learn dynamo module 4
Even though we have all unplaced views, we may not want to actually delete them all; there are sometimes views that users may need for whatever reason. To deal with this, we can write a keyword in the view name so that it is marked. In this example, I have used the string Keeper” which is shown in the Code Block. To filter views marked with this string, we first query all the view names with Element.Name. With the view names and keyword, we can feed these into String.Contains which will look through all the strings in the str input to see if any contain the string input in the searchFor parameter. If the input does contain the searchFor string then True will be output, otherwise it will be False.
We now have a list of booleans which will be True if the view contains Keeper” and False if not. With this list we can input it into the mask parameter of List.FilterBoolMask node which will split up a list of elements based on the mask list. All the unplaced views that were collected in part 3 will be separated into whether they contain the keyword or not, these will be output from the out parameter as they were marked False in the boolean list.

Deleting elements from a project file is always a little risky and extra precaution is recommended. Therefore, this part is simply another check point which is used to double check all the elements that are doomed with a series of Watch nodes. It is worth double checking them before deleting in part 6.


With all the elements collected, we need to join them together with List.Join and Flatten them before they head into the Python Script. The Boolean toggle is only used to reset the Python Script if we want to rerun it. The Python Script essentially deletes all our elements but for added security, I have wrapped the function in a Windows Form shown below. Before the script actually deletes the elements, the user will need to physically select all the elements in the form and click the Delete button on the form. If there is any doubt in the elements, they can be deselected or the script can be cancelled. The details of the Python Script can be found in part 6.1


Firstly we need to import the RevitAPI library so we can access the Elevation Marker methods.


The markers from input IN[0] are unwrapped using UnwrapElement which allows us to use them as Revit elements. We also make an output list which will be used to append all our elements in part 1.3.


A for loop is used to loop through each Elevation Marker (marker) in the markers list. A conditional is then used to check if the CurrentViewCount of the marker is equal (==) to 0, then append the element to the output list. This means we are only appending markers with no views associated with them. This list is then output using the OUT variable.


Here the incoming list of planes are unwrapped and set to the planes variable.  We also create an empty list which is set to the variable output. The input is retrieved as IN[0][0] as we only need one item of from the incoming list, which is one sheet.

Module 10

A for loop lets us loop through each Reference Plane (Plane) in the planes list. With each Reference Plane, we get it’s name with the get_Parameter function. This function takes a BuiltInParameter which in this case is DATUM_TEXT and we ensure the result value is a string with the AsString method. This builtin parameter is the name of the reference plane so we can check if the name is equal (==) to an empty string (“”), then append the plane to output list. This will filter out all the reference planes to collect only the ones with no name which is output using the OUT variable. 


As with the other Python scripts, we are simply unwrapping the incoming sheets and setting them to the sheet variable. A series of empty lists are also created which will be used later. It’s worth mentioning that all these empty lists can be created in one line, just separate each variable with a comma, for example, a,b,c = [].


A FilteredElementCollector is used to search the document (doc) of all elements that match the built in category for views (BuiltInCategory.OST_VIEWS). These views are then retrieved as elements (ToElements) and set to the collector variable.

 With all the views collector, we can then loop through each one in the collector list with a for loop. The IsTemplate method is used on each view to check if it is equal (==) to False as we don’t want templates. If it is not a template, then a series of if and elif conditionals are used. The first statement checks if the view’s view type (ViewType) is equal to FloorPlan. If this is true, it then uses and to check if the view can be added to a sheet with CanAddViewToSheet using the document (doc), sheet Id (sheet.Id) and view Id (view.Id). If either of these conditionals are False then the view will be passed over and if not, it will be added to the appropriate list(plans).
If the first if conditional is passed over, the script will move to the next elif statement which will perform the same function although this time, it will check if the view is of the CeilingPlan type. This cycle continues until we have gathered all the views of type FloorPlan, CeilingPlan, Section, Elevation and ThreeD(perspective). If the view is none of these, it is passed over. Finally all of the lists are output.

This script uses Window Forms so we need to import the appropriate library to do so. Here we add the System.Windows.Forms and System.Drawing libraries. Everything is imported from the Forms library along with the Point and Font classes from the Drawing library.

The inputs are toggle, which is used to reset the script, and the list of elements to be deleted (lst) which are unwrapped.
We then start of by creating a new Class called deleteCheckForm which inherits from the Form class. This basically means we are creating our own template for an object which adopts all of the functions and properties of a windows form. Every object in Python is defined by a class which sets out all the methods and properties of that object, basically a template for that object. 
To give you an example of this in imaginary life, say we wanted a Cat object. We would create that cat using a cat Class. The cat Class might define the base colour for that cat, number of whiskers and method to meow etc. We could then call that cat class whenever we like to create a cat object. We can then use that cat object to change it’s colour or make it meow…for example. 
So, back to real life, we are creating our version of a Form class which we will later use to create a Form object. If it doesn’t make sense, feel free it give me a yell.
The first function we are defining is the __init__ function which is called when we create a deleteCheckForm object. The parameter Self refers to the object that is created. In this function we can outline what the object does when it is first created. We first name it with self.Text, this will appear on the form title. We then set the Width and Height of the form as pixels, along with the Font. The Font parameter of our form takes a Font object as it’s type, so here we create a new Font object which is Arial size 5.0. This font size may need to be adjusted depending on your screen resolution.
We then tell our object to run the setupListBox function which is defined in part 6.7. This function basically creates a box and lists our input items.
In order to fill our form with pretty buttons we need to create them all. Here we create 4 by first instantiating a Button object and associating it to our form with self.delButton. The text to be shown on the button is set with the delbutton.Text property. We set the Location of the button which takes a point object so we need to create one with Point(). The parameters of the Point constructor is the location in pixels. This may need to be fiddled with to find the right spot if you intend to move the buttons. Next we create an event for delButton each time it is clicked by subscribing(+=) the Click event to run the self.delete method on the click event. This method is outlined in part 6.9.
This basic set of instructions is repeated 4 times to create 4 buttons. Each button subscribes to a separate Click event so we will later create the 4 methods that relate to these click events.

Besides buttons, we also want a label which instructs the user what to do. We create the label with Label() and set it to lab. Once created, the Text is updated to reflect what we want to display and the Location/Width are set so it is positioned in the correct place with enough width for the text to display.

To display all the buttons and label we just created, we need to add them to the Form’s Controls. This is done by using the Controls.Add function which was inherited in the Form. All the buttons and label are added to the form along with self.listbox. This is a Listbox element that was created with the setupListBox method from part 6.3 which is setup and explained in part 6.7
The AcceptButton from the form is set to the delButton that we created earlier. This basically means when the user hits the Enter key, the delButton is clicked.

The def keyword is used to define a new Function which in this case is called setupListBox. This function creates a new List Box component which we will use to display our list of elements on the form. First we create a ListBox element and assign it to listbox variable. We then set the Location, Width and Height of the Listbox which is all in pixel units. ScrollAlwaysVisible is set to False which means the scroll bar will only appear when there are too many items in the list. We also need to set the SelectionMode to the enum type SelectionMode.MultiExtended which allows us to select items on the listbox dynamically.


Here we need to add our input list to the form listbox. We do that by creating a for loop to cycle through each item in lst and Add the item to  the listbox.Items. Before the item is added though, it is wrapped as a Dynamo element using ToDSType and True as it was created in Revit. This just means that it will display with more information in Dynamo when the list pops up.


Next the delete function is created to respond to the delButton click created in 6.4. The parameters for this function are self ,sender and event. These relate to the form, the object that sent the request and the event that occurred respectively. Don’t worry about these too much though as they don’t have a huge bearing on this script. The first act of the function is to assign all the selected items in the list box to the selItems variable. We retrieve the selected items with the SelectedItems from the listbox component.

A for loop allows us to cycle through each selected item in selItems. On each loop, we Try to delete the item. To do this we first unwrap the element so it can be used with the Revit API. A transaction is then started using EnsureInTransaction and the document (doc) as the argument. This allows us to use the Delete function with the element Id as the argument (i.Id). If the delete is successful, a string is appended to output and the transaction is ended. If the delete fails, the except block is picked up and the Exception is assigned to e. We can use e to display the error in a string and append it to the output list.  This is particularly useful when Reference Planes are deleted as any plane hosting an element will fail at this point but the script can continue running.
Lastly the Windows Form will Close once the delete method has been run.

The Cancel method run if the canButton is clicked which was created in part 6.4. Basically, all this button does is run the Close() method from the form which will mean no other method is executed and the form closes.


Next the selAll function is created which is run if the selButton is clicked which was created in part 6.4. This function starts a for loop which runs through a range of numbers that is equivalent to the number of items in the input list (listbox.Items.Count). In each loop we use the SetSelected function from the listbox to set each item in the list to True , which means selected. The reason we looped through a range of numbers is because the SetSelected method requires an index(i) to set to True and therefore we are looping through every possible index.


Lastly, the desAll method is created which runs when we click the desAllButton created in part 6.4. This is essentially the same method as selAll however, this time it uses False in the SetSelected method which means all items in the list are deselected.

With our fancy new class created, we can now put it to use. If the toggle input is set to True, an empty list is created and set to output and a new form is created using the deleteCheckForm() class that we have developed up to this point. This new form object is set to the form variable. Next we use the Application.Run method from the Windows.Forms namespace with our form as the parameter. This basically makes the form visible to the user. Once the form has completed running, the output list is output.
If toggle is False then a simple string it output.

To reiterate what was said in the intro, an important thing to keep in mind when deleting views from the project is that it will fail if you try to delete the view your project is pointing at. Also, any reference plane that is hosting an element will not delete as well although that works in our favour.

Once you hit the Delete button on the windows form, all the selected elements will be deleted. If you happen to change your mind after you have deleted the elements, you can always switch over to Revit and undo the command. If you Cancel the windows form, the Python Script will basically do nothing so it can be a handy fail safe if you’ve run the script accidentally.

As always, if there is anything that didn’t quite make sense or you got stuck in following it along the way, please don’t hesitate to get in touch as I am always happy to help out. Also, if there’s anything I missed in terms of functionality, please let me know as I’m always looking to improve the scripts I work on. If you need to get in touch, you can contact me at or @LearnDynamo on Twitter. Cheers and please subscribe below for the latest on upcoming modules.




  1. Geom
    April 10, 2017 @ 9:32 am

    Great work!


  2. Neil
    April 10, 2017 @ 10:50 am

    Do the windows forms only work with Dynamo 1.3 or can I get these using Dynamo 1.2.
    Thanks again.


    • Jeremy
      April 10, 2017 @ 11:02 am

      No worries mate! Should work with any Dynamo version, as long as you are importing the System.Windows.Forms and System.Drawing libraries.


  3. Weekly Roundup – 2017.15 – The BIMsider - Revit news
    April 15, 2017 @ 4:22 pm

    […] Module 10 – Clean Project by Form […]


  4. Alberto tono
    April 20, 2017 @ 12:50 pm

    Thanks a lot,
    Amazing work.
    I am also interested in further developments.
    How fix the window form dimensions relating with the appropriate computer resolution.


    • Jeremy
      May 12, 2017 @ 11:49 pm

      thanks mate!


  5. Nick Burr
    May 5, 2017 @ 2:56 am

    Found this module very helpful. Could it also include schedule views attached to assemblies? Would you be able to determine which schedule is attached to which assembly for deletion?


    • Jeremy
      May 12, 2017 @ 11:55 pm

      Thanks Nick. I don’t see why not, you would have to filter views and check whether it is part of any assemblies. I don’t know the methods off the top of my head but happy to help further if you are giving it a crack.


Leave a Reply to Jeremy Cancel reply

Your email address will not be published. Required fields are marked *