Let's create 3D View graphic overviews for elements based on a parameter. We will need to isolate elements, override graphics, and create legends with the same colors.
Let's create 3D View graphic overviews for elements based on a parameter. We will need to isolate elements, override graphics, and create legends with the same colors.
Summary
Let's create a new tool
I'm very excited to keep sharing more information about views in Revit API, and now we will focus more on practical application.
I've shared the Revit model as well so you can follow along.
Let's create a tool that will generate 3D View Graphic Overviews by isolating and overriding the right elements. Also it will be very useful to create a Legend view that will describe all the colors.
We will do the following:
Get All Groups
Get Elements of Apartment Groups
Create new 3D View
Isolate Elements
Override Graphics based on the Group Name
Create Legend View
Create Text in Legend
Create Regions in Legend
Override Colors in Legend
It might seem like a lot of steps, but majority of them are easy or you already know how to do. So let's put it to practice and create a cool tool that will generate results like this:
Get All Groups
Let's begin by getting all groups in the project with FilteredElementCollctor.
You already know how to do it pretty well, but keep in mind that OfClass and OfCategory can return you different set of elements.
In my case, I've noticed that OfClass(Group)
also includes attached 2D groups, which might not always be ideal.
However, OfCategory(..)
is also collecting types, so don't forget to use WhereElementIsNotElementType to filter them out.
💡For this tool it doesn't matter which one we take, because we will filter all the groups with WHG in the name, which stand for apartment in German.
all_groups1 = FilteredElementCollector(doc).OfClass(Group).ToElements()
all_groups2 = FilteredElementCollector(doc)\
.OfCategory(BuiltInCategory.OST_IOSModelGroups)\
.WhereElementIsNotElementType()\
.ToElements()
print('Group 1: {}'.format(len(all_groups1)))
for g in all_groups1:
print(g.Name)
print('-'*50)
print('Group 2: {}'.format(len(all_groups2)))
for g in all_groups2:
print(g.Name)
After you checked the difference between OfClass/OfCategory you can decide which one you take and filter groups by 'WHG' in their names.
Here is the code that I needed for this step:
all_groups = FilteredElementCollector(doc).OfClass(Group).ToElements()
whg_groups = [g for g in all_groups if 'WHG' in g.Name]
print('WHG Groups: {}'.format(len(whg_groups)))
Create 3D View
Once we have the elements, we can create a new 3D View, where we will be working. Also, to make it nicer let's open this new view once the script execution is finished.
For that use uidoc.ActiveView property, and it has to be outside of the Transaction, because it doesn't make changes to the project, it just changes the Revit UI by opening another view.
t = Transaction(doc,'Create 3D Graphic Overviews')
t.Start()
view_type_3d_id = doc.GetDefaultElementTypeId(ElementTypeGroup.ViewType3D)
view_3d = View3D.CreateIsometric(doc, view_type_3d_id)
t.Commit()
uidoc.ActiveView = legend
💡Make sure you keep working inside of the Transaction between Start and Commit for next steps 😉
Isolate Elements
Now let's isolate the elements. But, there is no such method in Revit API…
But don't worry. We can get all elements that have to be hidden with .Excluding method and then we will HideElement.
And to make it more practical, let's make it a function that you will be able to reuse in your own scripts.
def isolate_elements(list_el_ids, view):
"""Isolate Provided Elements in a View by using .Excluding method in FEC."""
List_isolate_el_ids = List[ElementId](list_el_ids)
hide_elems = FilteredElementCollector(doc, view.Id).Excluding(List_isolate_el_ids).WhereElementIsNotElementType().ToElements()
hide_elem_ids = [el.Id for el in hide_elems if el.CanBeHidden(view)]
print('Hide: {}'.format(len(hide_elem_ids)))
List_hide_el_ids = List[ElementId](hide_elem_ids)
view.HideElements(List_hide_el_ids)
Now we need to get the elements to isolate. For that we will create an empty set to collect only unique ElementIds.
Let's iterate through each apartment group and get element ids with GetMemberIds Method.
💪And then, we can use our isolate_elements function.
isolate_elem_ids = set()
for whg in whg_groups:
member_ids = whg.GetMemberIds()
isolate_elem_ids.update(member_ids)
isolate_elements(list(isolate_elem_ids), view_3d)
Override Element Colors
Once you create a 3D View and your isolation works, we can start thinking about overriding colors. Let's iterate through apartments again and prepare a color for each of them.
💡random.randint will generate a random integer number between min and max values that you provide as arguments. After that it's about creating OverrideGraphicSettings and using SetElementOverrides.
Nothing new here for you 😉
for whg in whg_groups:
member_ids = whg.GetMemberIds()
import random
color = Color(random.randint(150, 255), random.randint(150, 255), random.randint(150, 255))
all_patterns = FilteredElementCollector(doc).OfClass(FillPatternElement).ToElements()
solid_pattern = [i for i in all_patterns if i.GetFillPattern().IsSolidFill][0]
overrides = OverrideGraphicSettings()
overrides.SetSurfaceForegroundPatternId(solid_pattern.Id)
overrides.SetSurfaceForegroundPatternColor(color)
for e_id in member_ids:
view_3d.SetElementOverrides(e_id, overrides)
This code snippet will generate this result. It might look fine, but keep in mind that our goal is to color all similar groups in the same color, but at the moment every group has a random color…
So we need to fix that.
Color Lib
To fix it, we need to create a dictionary that will associate colors with each group name. Then we will be able to get the same colors for the same group names!
Here is the code snippet to create color lib:
whg_color = {}
for whg in whg_groups:
if whg.Name not in whg_color:
color = Color(random.randint(150, 255), random.randint(150, 255), random.randint(150, 255))
whg_color[whg.Name] = color
Then inside the code make sure you change how you get your color.
color = whg_color[whg.Name]
👀 And now it looks way better and can tell you a story about how you organized your groups in the project. That's what we need!
Create Legend
Alright, we are done with the 3D View, and now let's have a look at how to create a Legend for these colors.
As you remember, there is no method to create a Legend, but we can get an existing one and duplicate it.
💪 And let's make it as a function, so you can reuse it any time you want in the future. It will need to:
Get All Views
Filter Legends
Ensure at least 1 Legend is in the project
Duplicate Legend
Update Name/Scale
def create_legend(scale = 50):
all_views = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Views).ToElements()
all_views = [view for view in all_views if not view.IsTemplate]
all_legends = [view for view in all_views if view.ViewType == ViewType.Legend]
if not all_legends:
forms.alert('There has to be at least 1 legend view in the project! '
'Please Create a legend and try again', exitscript=True)
random_legend = all_legends[0]
new_legend_id = random_legend.Duplicate(ViewDuplicateOption.Duplicate)
new_legend = doc.GetElement(new_legend_id)
new_legend.Scale = scale
return new_legend
legend = create_legend()
I didn't want to rename my legend, but if you want to, make sure you keep your name unique. For example you can keep adding * in the end until it becomes unique.
new_name = 'EF New View'
for i in range(10):
try:
new_legend.Name = new_name
break
except:
new_name += '*'
Create Text
Alright, we have an empty legend, now let's add a title that will say - Apartments Legend.
💡 We will need to create text multiple times, so it's a good idea to make it a function:
Here is the Snippet:
def create_text(view, pt, text, typeId=None):
"""Create a TextNote"""
if not typeId or typeId == ElementId(-1):
typeId = doc.GetDefaultElementTypeId(ElementTypeGroup.TextNoteType)
text_note = TextNote.Create(doc, view.Id, pt, text, typeId)
return text_note
X,Y = 0,0
title = create_text(legend, XYZ(X,Y,0), 'Apartments Legend:')
Create Text for each group
Now we need to iterate through our groups and keep changing the coordinate Y so text doesn't overlap.
for whg_name, color in whg_color.items():
Y -= 2
X = 0
whg_name = whg_name.replace(' (members excluded)', '')
text = create_text(legend, XYZ(X,Y,0), whg_name)
💡 Make sure you change your Y in the negative direction so new items appear below and not above your initial title.
Create Regions for each group
Now we need to create some filled regions and override their colors to match the color of the group.
Here is a function to Create Filled Regions:
def create_region(view, X, Y, region_width, region_height):
region_type_id = doc.GetDefaultElementTypeId(ElementTypeGroup.FilledRegionType)
points_0 = XYZ(X, Y, 0.0)
points_1 = XYZ(X+region_width, Y, 0.0)
points_2 = XYZ(X+region_width, Y-region_height, 0.0)
points_3 = XYZ(X, Y-region_height, 0.0)
points = [points_0, points_1, points_2, points_3, points_0]
list_boundary = List[CurveLoop]()
boundary = CurveLoop()
for n, point in enumerate(points):
if n == 4:
break
p1, p2 = points[n], points[n + 1]
boundary.Append(Line.CreateBound(p1, p2))
list_boundary.Add(boundary)
filled_region = FilledRegion.Create(doc, region_type_id, view.Id, list_boundary)
return filled_region
This function will:
Get Default Region Type Id
Generate Points based on provided starting coordinates and W/H size.
Turn Points into Boundary and eventually into List[CurveLoop]
And it will be used to create FilledRegion.
Now you can update the initial code to also create your Region next to text.
for whg_name, color in whg_color.items():
Y -= 2
X = 0
whg_name = whg_name.replace(' (members excluded)', '')
text = create_text(legend, XYZ(X,Y,0), whg_name)
X -= 2
region = create_region(legend, X,Y, 1, 0.5)
Override Element Colors
And the last step - Override colors of the regions in the legend.
Again, it's nothing new here for you, but let's make it a function because we already have the code to override your colors.
def override_element_surface(e_id, view, color):
all_patterns = FilteredElementCollector(doc).OfClass(FillPatternElement).ToElements()
solid_pattern = [i for i in all_patterns if i.GetFillPattern().IsSolidFill][0]
overrides = OverrideGraphicSettings()
overrides.SetSurfaceForegroundPatternId(solid_pattern.Id)
overrides.SetSurfaceForegroundPatternColor(color)
view.SetElementOverrides(e_id, overrides)
override_element_surface(region.Id, legend, color)
Results
And here is the final result that we wanted !
✨Final Code
And now let's put it all together to avoid any confusion.
Here is the code:
__title__ = "9.07 - Create Group Graphics Overviews"
__doc__ = """Date = 02.07.2024
_____________________________________________________________________
Description:
...
_____________________________________________________________________
Author: Erik Frits"""
from Autodesk.Revit.DB import *
from pyrevit import forms
import random
import clr
clr.AddReference('System')
from System.Collections.Generic import List
uidoc = __revit__.ActiveUIDocument
doc = __revit__.ActiveUIDocument.Document
def isolate_elements(list_el_ids, view):
"""Isolate Provided Elements in a View by using .Excluding method in FEC."""
List_isolate_el_ids = List[ElementId](list_el_ids)
hide_elems = FilteredElementCollector(doc, view.Id).Excluding(List_isolate_el_ids).WhereElementIsNotElementType().ToElements()
hide_elem_ids = [el.Id for el in hide_elems if el.CanBeHidden(view)]
print('Hide: {}'.format(len(hide_elem_ids)))
List_hide_el_ids = List[ElementId](hide_elem_ids)
view.HideElements(List_hide_el_ids)
def create_legend(scale = 50):
all_views = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Views).ToElements()
all_views = [view for view in all_views if not view.IsTemplate]
all_legends = [view for view in all_views if view.ViewType == ViewType.Legend]
if not all_legends:
forms.alert('There has to be at least 1 legend view in the project! '
'Please Create a legend and try again', exitscript=True)
random_legend = all_legends[0]
new_legend_id = random_legend.Duplicate(ViewDuplicateOption.Duplicate)
new_legend = doc.GetElement(new_legend_id)
new_legend.Scale = scale
return new_legend
def create_text(view, pt, text, typeId=None):
"""Create a TextNote"""
if not typeId or typeId == ElementId(-1):
typeId = doc.GetDefaultElementTypeId(ElementTypeGroup.TextNoteType)
text_note = TextNote.Create(doc, view.Id, pt, text, typeId)
return text_note
def create_region(view, X, Y, region_width, region_height):
region_type_id = doc.GetDefaultElementTypeId(ElementTypeGroup.FilledRegionType)
points_0 = XYZ(X, Y, 0.0)
points_1 = XYZ(X+region_width, Y, 0.0)
points_2 = XYZ(X+region_width, Y-region_height, 0.0)
points_3 = XYZ(X, Y-region_height, 0.0)
points = [points_0, points_1, points_2, points_3, points_0]
list_boundary = List[CurveLoop]()
boundary = CurveLoop()
for n, point in enumerate(points):
if n == 4:
break
p1, p2 = points[n], points[n + 1]
boundary.Append(Line.CreateBound(p1, p2))
list_boundary.Add(boundary)
filled_region = FilledRegion.Create(doc, region_type_id, view.Id, list_boundary)
return filled_region
def override_element_surface(e_id, view, color):
all_patterns = FilteredElementCollector(doc).OfClass(FillPatternElement).ToElements()
solid_pattern = [i for i in all_patterns if i.GetFillPattern().IsSolidFill][0]
overrides = OverrideGraphicSettings()
overrides.SetSurfaceForegroundPatternId(solid_pattern.Id)
overrides.SetSurfaceForegroundPatternColor(color)
view.SetElementOverrides(e_id, overrides)
all_groups = FilteredElementCollector(doc).OfClass(Group).ToElements()
whg_groups = [g for g in all_groups if 'WHG' in g.Name]
t = Transaction(doc,'Create 3D Graphic Overviews')
t.Start()
view_type_3d_id = doc.GetDefaultElementTypeId(ElementTypeGroup.ViewType3D)
view_3d = View3D.CreateIsometric(doc, view_type_3d_id)
isolate_elem_ids = set()
for whg in whg_groups:
member_ids = whg.GetMemberIds()
isolate_elem_ids.update(member_ids)
isolate_elements(list(isolate_elem_ids), view_3d)
whg_color = {}
for whg in whg_groups:
if whg.Name not in whg_color:
color = Color(random.randint(150, 255), random.randint(150, 255), random.randint(150, 255))
whg_color[whg.Name] = color
for whg in whg_groups:
member_ids = whg.GetMemberIds()
color = whg_color[whg.Name]
for e_id in member_ids:
override_element_surface(e_id, view_3d, color)
legend = create_legend()
X,Y = 0,0
title = create_text(legend, XYZ(X,Y,0), 'Apartments Legend:')
for whg_name, color in whg_color.items():
Y -= 2
X = 0
whg_name = whg_name.replace(' (members excluded)', '')
text = create_text(legend, XYZ(X,Y,0), whg_name)
X -= 2
region = create_region(legend, X,Y, 1, 0.5)
override_element_surface(region.Id, legend, color)
t.Commit()
uidoc.ActiveView = legend
I hope you find this tool useful in your own work. But now, I want you to adjust it to your own needs.
Select another set of elements and take another parameter, that will be responsible for the color and create a 3D Graphic Overview for that.
For example you can grab all walls and override them based on the fire rating, or take your windows and override based on their Type Name.
Make it useful for you!