3D View Overviews

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.

3D View Overviews

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.

Download Files

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.

#👉 Get and Filter Groups
all_groups1 = FilteredElementCollector(doc).OfClass(Group).ToElements()
all_groups2 = FilteredElementCollector(doc)\
                            .OfCategory(BuiltInCategory.OST_IOSModelGroups)\
                            .WhereElementIsNotElementType()\                            
                            .ToElements()
                            
# Print Group 1                            
print('Group 1: {}'.format(len(all_groups1)))
for g in all_groups1:
    print(g.Name)
    
print('-'*50) #Separator ------------------------------

# Print Group 2
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:

#👉 Get and Filter Apartment Groups
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.

# Start Transaction
t = Transaction(doc,'Create 3D Graphic Overviews')
t.Start()   #🔓

#📰 Create New 3D View
view_type_3d_id = doc.GetDefaultElementTypeId(ElementTypeGroup.ViewType3D)
view_3d         = View3D.CreateIsometric(doc, view_type_3d_id)

t.Commit()

#✅ Open New 3D View
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."""
    # 🔎 Get Elements To Hide with .Excluding(isolate_elems)
    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)]

    #🚫 Hide Elements
    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.

#👉 Get Elements to Isolate
isolate_elem_ids = set()
for whg in whg_groups:
    member_ids = whg.GetMemberIds()
    isolate_elem_ids.update(member_ids)

#🚫 Isolate Elements
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 😉

#🎨 Override WHG Colors
for whg in whg_groups:
    member_ids = whg.GetMemberIds()
    #🖌️ Override Color
    import random
    color = Color(random.randint(150, 255), random.randint(150, 255), random.randint(150, 255))
    

    # Pattern
    all_patterns  = FilteredElementCollector(doc).OfClass(FillPatternElement).ToElements()
    solid_pattern = [i for i in all_patterns if i.GetFillPattern().IsSolidFill][0]

    # Create Overrides
    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:

#🎨 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.

#❌ Before - Random Color
#color = Color(random.randint(150, 255), random.randint(150, 255), random.randint(150, 255))

#✅ Specific Color for each Group.Name
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]

    # ✅ Check Legend in the Project
    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)

    #🎭 Duplicate Random Legend
    random_legend = all_legends[0] #type: View
    new_legend_id = random_legend.Duplicate(ViewDuplicateOption.Duplicate)
    new_legend    = doc.GetElement(new_legend_id)

    # Set Name and Scale
    new_legend.Scale = scale
    # new_legend.Name = 'Bla-Bla-Bla'

    return new_legend


#📰 Create 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.

#*️⃣ RENAME NEW VIEW
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

#📍 Starting Coordinate
X,Y = 0,0

#🖼️ Create Legend Elements
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 #feet
    X  = 0

    # Create WHG Name Text
    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):
    # VARIABLES
    region_type_id = doc.GetDefaultElementTypeId(ElementTypeGroup.FilledRegionType)

    # REGION POINTS
    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]

    # CREATE LIST BOUNDARY
    list_boundary = List[CurveLoop]()
    boundary      = CurveLoop()

    for n, point in enumerate(points):
        if n == 4:
            break

        # LINE POINTS
        p1, p2 = points[n], points[n + 1]

        # LINE
        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 #feet
    X  = 0

    # Create WHG Name Text
    whg_name = whg_name.replace(' (members excluded)', '')
    text     = create_text(legend, XYZ(X,Y,0), whg_name)

    # Create Filled Region
    X -= 2 # Feet
    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):
    # Pattern
    all_patterns  = FilteredElementCollector(doc).OfClass(FillPatternElement).ToElements()
    solid_pattern = [i for i in all_patterns if i.GetFillPattern().IsSolidFill][0]

    # Create Overrides
    overrides = OverrideGraphicSettings()
    overrides.SetSurfaceForegroundPatternId(solid_pattern.Id)
    overrides.SetSurfaceForegroundPatternColor(color)

    # Override Color
    view.SetElementOverrides(e_id, overrides)

#🖌️ Set Region 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:

# -*- coding: utf-8 -*-
__title__   = "9.07 - Create Group Graphics Overviews"
__doc__ = """Date    = 02.07.2024
_____________________________________________________________________
Description:
...
_____________________________________________________________________
Author: Erik Frits"""

# ╦╔╦╗╔═╗╔═╗╦═╗╔╦╗╔═╗
# ║║║║╠═╝║ ║╠╦╝ ║ ╚═╗
# ╩╩ ╩╩  ╚═╝╩╚═ ╩ ╚═╝ IMPORTS
#==================================================
from Autodesk.Revit.DB import *
from pyrevit import forms
import random

import clr
clr.AddReference('System')
from System.Collections.Generic import List

# ╦  ╦╔═╗╦═╗╦╔═╗╔╗ ╦  ╔═╗╔═╗
# ╚╗╔╝╠═╣╠╦╝║╠═╣╠╩╗║  ║╣ ╚═╗
#  ╚╝ ╩ ╩╩╚═╩╩ ╩╚═╝╩═╝╚═╝╚═╝ VARIABLES
#==================================================
uidoc  = __revit__.ActiveUIDocument
doc    = __revit__.ActiveUIDocument.Document #type:Document


# ╔═╗╦ ╦╔╗╔╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
# ╠╣ ║ ║║║║║   ║ ║║ ║║║║╚═╗
# ╚  ╚═╝╝╚╝╚═╝ ╩ ╩╚═╝╝╚╝╚═╝

def isolate_elements(list_el_ids, view):
    """Isolate Provided Elements in a View by using .Excluding method in FEC."""
    # 🔎 Get Elements To Hide with .Excluding(isolate_elems)
    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)]

    #🚫 Hide Elements
    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]

    # ✅ Check Legend in the Project
    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)

    #🎭 Duplicate Random Legend
    random_legend = all_legends[0] #type: View
    new_legend_id = random_legend.Duplicate(ViewDuplicateOption.Duplicate)
    new_legend    = doc.GetElement(new_legend_id)

    # Set Name and Scale
    new_legend.Scale = scale
    # new_legend.Name = 'Bla-Bla-Bla'

    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):
    # VARIABLES
    region_type_id = doc.GetDefaultElementTypeId(ElementTypeGroup.FilledRegionType)

    # REGION POINTS
    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]

    # CREATE LIST BOUNDARY
    list_boundary = List[CurveLoop]()
    boundary      = CurveLoop()

    for n, point in enumerate(points):
        if n == 4:
            break

        # LINE POINTS
        p1, p2 = points[n], points[n + 1]

        # LINE
        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):
    # Pattern
    all_patterns  = FilteredElementCollector(doc).OfClass(FillPatternElement).ToElements()
    solid_pattern = [i for i in all_patterns if i.GetFillPattern().IsSolidFill][0]

    # Create Overrides
    overrides = OverrideGraphicSettings()
    overrides.SetSurfaceForegroundPatternId(solid_pattern.Id)
    overrides.SetSurfaceForegroundPatternColor(color)

    # Override Color
    view.SetElementOverrides(e_id, overrides)


# ╔╦╗╔═╗╦╔╗╔
# ║║║╠═╣║║║║
# ╩ ╩╩ ╩╩╝╚╝ MAIN
#==================================================

#👉 Get and Filter Apartment Groups
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)))
# 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)

# Start Transaction
t = Transaction(doc,'Create 3D Graphic Overviews')
t.Start()   #🔓

#📰 Create New 3D View
view_type_3d_id = doc.GetDefaultElementTypeId(ElementTypeGroup.ViewType3D)
view_3d         = View3D.CreateIsometric(doc, view_type_3d_id)

#👉 Get Elements to Isolate
isolate_elem_ids = set()
for whg in whg_groups:
    member_ids = whg.GetMemberIds()
    isolate_elem_ids.update(member_ids)

#🚫 Isolate Elements
isolate_elements(list(isolate_elem_ids), view_3d)

#🎨 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


#🎨 Override WHG Colors
for whg in whg_groups:
    member_ids = whg.GetMemberIds()
    #🖌️ Override Color
    color = whg_color[whg.Name]
    for e_id in member_ids:
        override_element_surface(e_id, view_3d, color)

    # # Pattern
    # all_patterns  = FilteredElementCollector(doc).OfClass(FillPatternElement).ToElements()
    # solid_pattern = [i for i in all_patterns if i.GetFillPattern().IsSolidFill][0]
    #
    # # Create Overrides
    # overrides = OverrideGraphicSettings()
    # overrides.SetSurfaceForegroundPatternId(solid_pattern.Id)
    # overrides.SetSurfaceForegroundPatternColor(color)
    #
    # for e_id in member_ids:
    #     view_3d.SetElementOverrides(e_id, overrides)


#📰 Create Legend
legend = create_legend()

#📍 Starting Coordinate
X,Y = 0,0

#🖼️ Create Legend Elements
title = create_text(legend, XYZ(X,Y,0), 'Apartments Legend:')

for whg_name, color in whg_color.items():
    Y -= 2 #feet
    X  = 0

    # Create WHG Name Text
    whg_name = whg_name.replace(' (members excluded)', '')
    text     = create_text(legend, XYZ(X,Y,0), whg_name)

    # Create Filled Region
    X -= 2 # Feet
    region = create_region(legend, X,Y, 1, 0.5)

    #🖌️ Set Region Overrides
    override_element_surface(region.Id, legend, color)

t.Commit()  #🔒

#✅ Open New 3D View
uidoc.ActiveView = legend

# Happy Coding!

HomeWork

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!

⌨️ Happy Coding!

Discuss the lesson:

P.S. Sometimes this chat might experience connection issues.
Please be patient or join via Discord app so you can get the most out of this community and get access to even more chats.

P.S. Sometimes this chat might experience connection issues.
Please be patient or join via Discord app so you can get the most out of this community and get access to even more chats.

© 2023-2024 EF Learn Revit API

© 2023-2024 EF Learn Revit API

© 2023-2024 EF Learn Revit API