While on the forums, one thing I don’t see much of is using sets in their code where it is ideal (either in Dynamo Built-In Nodes or in python). Most of the time you see simple things made complicated with for loops where a set operation would have made life sooo much easier (<cough> yes, I was one of these people too). If you haven’t come across sets before then you are in for a treat as they are super powerful in programming (and in mathematics) for all kinds of operations.
Firstly, for those not in the know, let’s define what a set is…
A Set is a collection of objects (similar to a list or an array if you will) that have no duplicates and are in no particular order. This is useful as you can create a distinct (aka unique) Set of objects where there are no duplicates – if you python, you have probably used Sets before to do just this, but might not have realised the other great features like Union, Intersection and Difference and these are what makes Sets so awesome. In fact, the FilteredElementCollector is a set, have a look at the API docs and you’ll see this has UnionWith() and IntersectWith() and Excluding() methods that do pretty much the same thing as Set.Union(), Set.Intersection() and Set.Difference()…if you use the FilteredElementCollector you have unwittingly already been using sets all along. However, in this post I will be referring to the set() type in python…
NOTE: I won’t go to far into explaining all these myself (mostly because I’m lazy and partly because this post will get super long and also there are already very good explanations like this one that does a far more comprehensive explanation than I would have given – you know, because I’m lazy and stuff and I need to go cook soon and the post will get super long and stuff). What I will show here however, is some examples of how you can use some of these features within python and in the context of the Revit API to do useful things… mostly with differences as I use these more often and I have more practical examples to share!
Set() Examples…
ViewTemplates | GetUnused | Py
This code (and the next few examples) uses set.difference to determine which ViewTemplates are NOT being used in your project by first getting all the ViewTemplates in the project, then getting the ViewTemplates for all the views that have a ViewTemplate applied and then doing a difference between the two sets (don’t forget that nice feature of sets that all objects are unique in a set – so no duplicates). If all ViewTemplates are being used then both sets will have the same objects in them and a difference will give you an empty list, however, if one or more ViewTemplates are not being used then the set with ALL the ViewTemplates will be one or more objects bigger and doing a difference will give use the ones NOT being used… I hope that made sense! 😀
"""
Description: Finds all the ViewTemplates NOT being used in the current Document.
Author: Dan Woodcock
Website: https://danimosite.wordpress.com
Licence: N/A
"""
###### Imports ######
import clr
clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
doc = DocumentManager.Instance.CurrentDBDocument
clr.AddReference("RevitAPI")
from Autodesk.Revit.DB import *
###### Main ######
"""
Get all views in the current Document including templates..
NOTE: ViewTemplates are a typeof View. When getting views using the FilteredElementCollector by OfClass(View), this will include the ViewTemplates too as well as the views you would use in Revit - many people have and will get caught out by this at some point. If you want to filter out ViewTemplates right off the bat, then do this...
views = [v for v in FilteredElementCollector(doc).OfClass(View).ToElements() if not v.IsTemplate]
"""
allViews = FilteredElementCollector(doc).OfClass(View).ToElements()
# Filter all the ViewTemplates from allViews...
templateIds = [v.Id for v in allViews if v.IsTemplate]
"""
From the Views that are NOT templates, get the ViewTemplateId of the views that ONLY have a valid ViewTemplateId.
An Invalid ElementId has an IntegerValue of -1, but it is best practice to use the conditional
if not v.ViewTemplateId == ElementId.InvalidElementId
to filter them out rather than by the conditional
if v.ViewTemplateId.IntegerValue == -1
"""
usedTemplateIds = [v.ViewTemplateId for v in allViews if not v.IsTemplate and not v.ViewTemplateId == ElementId.InvalidElementId]
"""
Now we will create sets from both lists of ElementIds and do a difference between the sets. Sets are a mathematical construct used frequently in programming to create lists of distinct (or unique) elements, for example, take the list...
[1,1,1,2,2,3,3,3,4,5,5,5]
converting to a set, the list will become...
[1,2,3,4,5]
With this in mind, imagine a difference between sets, that is where set is subtracted from one another. This is something unique to sets and is very useful! For example, take the following sets...
setA = [1,2,3,4,5]
setB = [3,4,5,6,7]
a difference between setA.difference(setB) will return...
setA-SetB = [1,2]
and conversely, a difference between setB.difference(setA) will return...
setB-SetA = [6,7]
You can see that you are left with the non-common elements of the set, and the which way you apply the difference IS IMPORTANT.
Now, in the context of finding which ViewTemplates are being used, we have a list of ViewTemplateIds being used and a list of ALL the ViewTemplateIds. Now, we want to subtract the UsedViewTemplateIds from the set of ALL the ViewTemplateIds and this will give us all the unused ViewTemplateIds...make sense? Ha, maybe...maybe not, if not, have a look above again at the set difference example.
"""
unusedTemplates = set(templateIds) - set(usedTemplateIds)
###### Output ######
# We get the Element from the ElementId and return a dictionary of [ViewTemplate name, ViewTemplate object] to be used downstream in the graph...
OUT = dict(zip([doc.GetElement(id).Name for id in unusedTemplates], [doc.GetElement(id) for id in unusedTemplates]))
Filters | GetUnused | Py
Similar to the above, but with Filters…
"""
Description: Finds all the Filters NOT being used in the current Document.
Author: Dan Woodcock
Website: https://danimosite.wordpress.com
Licence: N/A
"""
###### Imports ######
import clr
clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
doc = DocumentManager.Instance.CurrentDBDocument
clr.AddReference("RevitAPI")
from Autodesk.Revit.DB import *
###### Main ######
"""
Get all views in the current Document that are NOT a ViewTemplate.
NOTE: ViewTemplates are a typeof View. When getting views using the FilteredElementCollector by OfClass(View), this will include the ViewTemplates too as well as the views you would use in Revit - many people have and will get caught out by this at some point.
"""
views = [x for x in FilteredElementCollector(doc).OfClass(View) if not x.IsTemplate]
# Get all Filter Ids in the current Document...
allFilters = [f.Id for f in FilteredElementCollector(doc).OfClass(FilterElement)]
# Check each view for applied Filters and add to the usedFilters list...
usedFilters = []
for v in views:
try:
# Get all the filters applied to the View, this is a list of ElementIds...
filters = v.GetFilters()
# If the View does have filters then add the usedFilters...
if not filters == None and len(filters) > 0:
for f in fills:
usedFilters.append(f)
# Some views do not allow filters, we are using the try>except statements here, we don't care if an exception is caught here so we just skip to the next View...
except:
pass
"""
Now we will create sets from both lists of ElementIds and do a difference between the sets. Now, in the context of finding which FilterElements are being used, we have a list of FilterElementIds being used and a list of ALL the FilterElementIds. Now, we want to subtract the UsedFilterElementIds from the set of ALL the FilterElementIds and this will give us all the unused FilterElementIds...make sense?
"""
unusedFilters = set(allFilters) - set(usedFilters)
###### Output ######
# We get the Element from the ElementId and return a dictionary of [FilterElement name, FilterElement object] to be used downstream in the graph...
OUT = dict(zip([doc.GetElement(i).Name for i in unusedFilters], [doc.GetElement(i) for i in unusedFilters]))
View | NotOnSheet | Py
This uses the same premise as before, but is a to get ALL views NOT on sheet. A useful thing to know when you want to do some housekeeping…
"""
Description: Finds all the Graphical Views NOT on sheet in the current Document.
Author: Dan Woodcock
Website: https://danimosite.wordpress.com
Licence: N/A
"""
###### Imports ######
import clr
clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
doc = DocumentManager.Instance.CurrentDBDocument
clr.AddReference("RevitAPI")
from Autodesk.Revit.DB import *
###### Main ######
# ViewTypes of the views we want to filter by...
graphicalViewTypes = [ViewType.AreaPlan, ViewType.CeilingPlan, ViewType.ColumnSchedule, ViewType.Detail, ViewType.DraftingView, ViewType.Elevation, ViewType.EngineeringPlan, ViewType.FloorPlan, ViewType.Rendering, ViewType.Section, ViewType.ThreeD, ViewType.Walkthrough]
# The Views that have ViewTypes that match the graphicalViewType filter...
views = [v.Id for v in FilteredElementCollector(doc).OfClass(View).ToElements() if not v.IsTemplate and v.ViewType in graphicalViewTypes]
# The views that are on sheet and have ViewTypes that match the graphicalViewTypes filter...
viewsOnSheet = [v.ViewId for v in FilteredElementCollector(doc).OfClass(Viewport).ToElements() if doc.GetElement(v.ViewId).ViewType in graphicalViewTypes]
# Do a difference between sets between views that exist and views that have viewports...
notOnSheet = set(views) - set(viewsOnSheet)
###### Output ######
# Return a dictionary of the View Name and the View Element to be used downstream...
OUT = dict(zip([doc.GetElement(x).Name for x in notOnSheet], [doc.GetElement(x) for x in notOnSheet]))
Legend | NotOnSheet | Py
Again, similar to the Views NotOnSheet example, but targeted specifically for Legends as there is a simpler way to write this…
"""
Description: Finds all the Legends NOT on sheet in the current Document.
Author: Dan Woodcock
Website: https://danimosite.wordpress.com
Licence: N/A
"""
###### Imports ######
import clr
clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
doc = DocumentManager.Instance.CurrentDBDocument
clr.AddReference("RevitAPI")
from Autodesk.Revit.DB import *
###### Main ######
# All the legend views in the project. This is NOT the legends on sheet, but the legends that are in the Legends section in the project browser...
legends = [v.Id for v in FilteredElementCollector(doc).OfClass(View).ToElements() if v.ViewType == ViewType.Legend]
# All the Legends that are placed on sheet...
legendsOnSheet = [x.ViewId for x in FilteredElementCollector(doc).OfClass(Viewport).ToElements() if doc.GetElement(x.ViewId).ViewType == ViewType.Legend]
# A difference of the legends views that are in the project and the legends placed on sheet...
notOnSheet = set(legends) - set(legendsOnSheet)
###### Output ######
# Return a dictionary of the Legend names and legend Elements not on sheet to be used downstream...
OUT = dict(zip([doc.GetElement(x).Name for x in notOnSheet], [doc.GetElement(x) for x in notOnSheet]))
Schedule | NotOnSheet | Py
And this again is self explanatory, it gets all schedules not on sheet. Again, I have included this as the way I’m getting the schedules is different from the other examples and might be useful to you…
"""
Description: Finds all the Schedules NOT on sheet in the current Document.
Author: Dan Woodcock
Website: https://danimosite.wordpress.com
Licence: N/A
"""
###### Imports ######
import clr
clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
doc = DocumentManager.Instance.CurrentDBDocument
clr.AddReference("RevitAPI")
from Autodesk.Revit.DB import *
###### Main ######
# Get all the ViewSchedules in the current document, these are not the Schedules placed on sheet, but the schedules in the the schedule section in the project browser...
schedules = [v.Id for v in FilteredElementCollector(doc).OfClass(ViewSchedule).ToElements() if not v.IsTitleblockRevisionSchedule]
# Get the Schedules actually placed on sheet. These are of the class ScheduleSheetInstance and we are also filtering out the Revision Schedules as we don't want these...
schedulesOnSheet = [v.ScheduleId for v in FilteredElementCollector(doc).OfClass(ScheduleSheetInstance).ToElements() if not v.IsTitleblockRevisionSchedule]
# Do a difference between ViewSchedules in the project and the schedules placed on sheet. This will give us all the schedules NOT placed on sheet...
notOnSheet = set(schedules) - set(schedulesOnSheet)
###### Output ######
# Return a dictionary of the Schedule Name and Schedule Element not on sheet to use downstream...
OUT = dict(zip([doc.GetElement(x).Name for x in notOnSheet], [doc.GetElement(x) for x in notOnSheet]))
SetsExample | Windows | Py
And for the final example, I am using set.instersection… this is purely for demonstration to show how you can use an intersection and a difference to show which Window Elements are in view and which ones are not in view…
"""
Description: This is an example of using set.intersection and set.difference to find which windows are and are not in view.
Author: Dan Woodcock
Website: https://danimosite.wordpress.com
Licence: N/A
"""
###### Imports ######
import clr
clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
doc = DocumentManager.Instance.CurrentDBDocument
clr.AddReference("RevitAPI")
from Autodesk.Revit.DB import *
###### Definitions ######
def ToElements(eids):
return [doc.GetElement(id) for id in eids]
###### Main ######
# A list of all the FamilyInstance Ids in the Active View...
fis = [x.Id for x in FilteredElementCollector(doc, doc.ActiveView.Id).OfClass(FamilyInstance).ToElements()]
# A list of all the Window Ids in the project...
allWindows = [x.Id for x in FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Windows).WhereElementIsNotElementType().ToElements()]
"""
Here we are getting the intersection between all the FamilyInstances in View and the Windows in the project. This will give us all the windows in the View.
NOTE: This is just for demonstration using sets, you can write this more consicely with the FilteredElementCollector like so..
windowsInView = FilteredElementCollector(doc, doc.ActiveView.Id).OfCategory(BuiltInCategory.OST_Windows).ToElements()
The FilteredElementCollector itself is a set and you can do unions, intersections and differences on the collector itself.
"""
windowsInView = set(fis) & set(allWindows)
# Here we are getting the set difference between the allWindows set and the windowsInView set. This will give us all the windows NOT in view...
windowsNotInView = set(allWindows) - windowsInView
OUT = ToElements(windowsInView), ToElements(windowsNotInView)
Well, that’s about it…the final closing statement is that you should use sets! They are super useful!
I hope you found these little examples useful and you experiment with sets yourself and of course, thanks for reading! 🙂
May 1, 2021 at 7:18 am
Hi Dan, I am a young engineer from Laos, a small country in South East Asia near China. I just come across your WordPress. Your place are mining of gold! A lot of knowledge inside. Thanks for running this good stuff.
LikeLiked by 1 person