Create Sheets & Viewports

Placing views on sheets is not fun in Revit. Unless you use python to automate this step. Then it becomes fun, so let's learn how to do that.

Create Sheets & Viewports

Placing views on sheets is not fun in Revit. Unless you use python to automate this step. Then it becomes fun, so let's learn how to do that.

Download Files

Summary

In this lesson, we will learn how to create sheets and place views on them. I will also show you how to get the sizes of Title Blocks and Views so you can calculate their positions when you place them.

Let's begin by exploring documentation.

Docs: How to Create Sheets and Place Views?

Mainly, we will need to work with the ViewSheet and Viewport classes in this lesson.

To create a sheet in Revit API, we need to inspect the Create(doc, title_block_id) method of the ViewSheet Class. It's very straightforward and easy to use.

To place a view on the sheet, we would need to have a look at the Viewport Class.

It's a class that establishes the placement of a view on a sheet. So, to place a view on the sheet, we would need to Create a Viewport.
It needs 4 arguments: doc, view_sheeet_id, view_id, point

Create ViewSheet

Let's start by creating a sheet. For that, we need an ElementId of a TitleBlock.

There are 2 ways we can get them:

1️⃣ Get All TitleBlock Types with FilteredElementCollector
2️⃣ Or, get a default TitleBlock Type with GetDefaultFamilyTypeId method

Once you choose your TitleBlock Type, you can create new sheet like this:

#📰 Get TitleBlocks
all_titleblocks = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_TitleBlocks)\
    .WhereElementIsElementType().ToElements()
default_title_block_id = doc.GetDefaultFamilyTypeId(ElementId(BuiltInCategory.OST_TitleBlocks))

#🔓 Start Transaction
t= Transaction(doc, 'Create New Sheet')
t.Start() #🔓

#📰 Create Sheet
new_sheet = ViewSheet.Create(doc, default_title_block_id)
print('Created New Sheet: {} - {}'.format(new_sheet.SheetNumber, new_sheet.Name))

t.Commit() #🔒
Rename ViewSheet

Renaming Sheets is similar to renaming Views. We can use the SheetNumber and Name properties.

Keep in mind that the SheetNumber has to be unique, while the Name can be repeated across multiple sheets.

new_sheet.SheetNumber = 'Unique SheetNumber'
new_sheet.Name        = 'Can be Repeated'
Get Views to Place on the Sheet

Now that we know how to create sheets, let's get some views that we could place.

💡This step will depend on the script that you want to create. Usually, when you create a bunch of views or sections and you want to place them on the sheet, it's simple.

But in this lesson I will keep it very simple and grab a few sections that I prepared with their ElementIds. I can inspect these views with RevitLookup and rewrite their Ids.

#👉 Get Views
elev = doc.GetElement(ElementId(395398))
cros = doc.GetElement(ElementId(395407))
plan = doc.GetElement(ElementId(395416))
View Positions on Sheet

We are nearly ready to place views on the sheet. We just need to define their positions with the XYZ class.

Firstly, let's keep it simple and manually define these points. It works great when you know that your views will all have similar structure or sizes and you can use the same fixed positions for them. For example, I did when I created sections for my windows, and it was great in 99% of cases.

So for this example, I've manually placed my views on the sheet and then inspected the Viewports with Revit Lookup to find the right coordinates by looking at GetBoxCenter method.

Here are my coordinates:

# ⏺️ Define Viewport positions
pt_elev = XYZ(-1.00, 0.65, 0)
pt_cros = XYZ(-0.75, 0.65, 0)
pt_plan = XYZ(-1.00, 0.25, 0)

💡 Also, keep in mind where the Origin point of your TitleBlock is located. Since mine is located on the bottom right corner, it means I have to move along the negative X-Axis and the positive Y-Axis.

Place Views on Sheet

As I've mentioned, to place views on sheets we need to Create Viewport instances. It's fairly simple to do, as we need to provide doc, new_sheet_id, view_id and xyz_position arguments.

Also, it's a good idea to always check if a View can be added by using CanAddViewToSheet(doc, view_sheet_id, view_id) method.

# 🖼️ Place Views on Sheets (Viewport.Create)
if Viewport.CanAddViewToSheet(doc, new_sheet.Id, elev.Id):
    viewport_elev = Viewport.Create(doc, new_sheet.Id, elev.Id, pt_elev)
    viewport_cros = Viewport.Create(doc, new_sheet.Id, cros.Id, pt_cros)
    viewport_plan = Viewport.Create(doc, new_sheet.Id, plan.Id, pt_plan)

Now you know how to create Sheets and place views on them.
Now, let's focus on calculating the positions.

We are interested in the paperscale sizes of our views and Title block. Let's begin with the title block.

TItleBlock Size

First of all, we need to get the title block from our sheet. There are different methods to get it, and you can read about it in this blog post.

There are 2 options you can use, but I will use the first one because it's quicker. Here is the function where we use ElementParameterFilter to get the Title Block.

def get_titleblocks_from_sheet(doc, sheet):
    # type:(Document, ViewSheet) -> list
    """Function to get TitleBlocks from the given ViewSheet.
    :return:      Return TitleBlock from provided Sheet"""

    # RULE ARGUMENTS
    rule_value         = sheet.SheetNumber
    param_sheet_number = ElementId(BuiltInParameter.SHEET_NUMBER)
    f_pvp              = ParameterValueProvider(param_sheet_number)
    evaluator          = FilterStringEquals()

    # CREATE A RULE (Method has changed in Revit API in 2022)
    f_rule = FilterStringRule(f_pvp, evaluator, rule_value)        # RVT 2022+
    tb_filter = ElementParameterFilter(f_rule)

    # GET TITLEBLOCKS
    tb = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_TitleBlocks) \
        .WhereElementIsNotElementType().WherePasses(tb_filter).FirstElement()

    return tb

Now we can easily get the titleblock from our newly created sheet and read it's SHEET_WIDTH and SHEET_HEIGHT parameters.

Since these are built-in parameters, all TitleBlocks have them, so we can easily get values with AsDouble()

#📏 Get TitleBlock Size
titleblock = get_titleblocks_from_sheet(doc, new_sheet)
tb_W = titleblock.get_Parameter(BuiltInParameter.SHEET_WIDTH).AsDouble()  # In Feet
tb_H = titleblock.get_Parameter(BuiltInParameter.SHEET_HEIGHT).AsDouble() # In Feet

💡 Some of you might want to get other type parameters to determine the size of your TitleBlocks. In this case, make sure you are getting parameters from the type and not the instance of your titleblocks!

#📏 Get TitleBlock Size
titleblock = get_titleblocks_from_sheet(doc, new_sheet)

# Get Type Parmaeters...
titleblock_typ = doc.GetElement(titleblock.GetTypeId())
Calculate View Size

To calculate the View size, we can grab one of the views and experiment in RevitPythonShell.

There are 2 things you might want to check: Outline and CropBox sizes of your views.
Let's begin by inspecting the Outline:

View.Outline (BoundingBoxUV)

View.Outline property will return you a BoundingBoxUV element. This is a 2-dimensional Area. Similar to BoundingBoxXYZ, but without 3rd dimension.

It's the outline which you can see around your Viewport when you hover over it. It will depend on CropBox + all the elements visible outside of the CropBox.

Outline has Min/Max points that define it, but in here we need to use properties U and V. And we can calculate Width and Height by substracting Max coordinates vs Min coordinates like this:

elev = doc.GetElement(ElementId(395398))

# Calculate Outline Size
outline = elev.Outline

v_min = outline.Min
v_max = outline.Max

W = v_max.U - v_min.U #feet
H = v_max.V - v_min.V #feet

w_cm = UnitUtils.ConvertFromInternalUnits(W, UnitTypeId.Centimeters)
h_cm = UnitUtils.ConvertFromInternalUnits(H, UnitTypeId.Centimeters)

print('Outline: {}, {}'.format(w_cm, h_cm))

Don't forget that units will be in Feet, so it might be a good idea to ConvertFromInternalUnits to compare the results.

View.CropBox (BoundingBoxXYZ)

We can also inspect the size of the CropBox of our views. We can get it with CropBox property, but this time we will get BoundingBoxXYZ Class. But we won't need Z coordinates for calculations as it's irrelevant.


💡You can also ensure that it property CropBoxActive is True.

elev = doc.GetElement(ElementId(395398))

# CropBox Size
BB = elev.CropBox

# Get Model H/W Size
W = BB.Max.X - BB.Min.X #feet
H = BB.Max.Y - BB.Min.Y #feet

# Convert To Paper Size (divide by View Scale)
W = W / elev.Scale
H = H / elev.Scale

# Convert to CM
w_cm = UnitUtils.ConvertFromInternalUnits(W, UnitTypeId.Centimeters)
h_cm = UnitUtils.ConvertFromInternalUnits(H, UnitTypeId.Centimeters)

print('CropBox cm: {}, {}'.format(w_cm, h_cm))
Function: Get View PaperSize

Now you know how to get TitleBlock and View sizes. I won't use View sizes in my example for position calculations. But you can easily turn it into a function to simplify the code like this:

def get_view_paper_size(view):
    """Get View's CropBox size in Paperscale
    :return tuple (W,H)"""
    if view.CropBoxActive:
        # CropBox Size
        BB = view.CropBox

        # Get Model H/W Size
        W = BB.Max.X - BB.Min.X #feet
        H = BB.Max.Y - BB.Min.Y #feet

        # Convert To Paper Size (divide by View Scale)
        W = W / view.Scale
        H = H / view.Scale

        return (W,H)


#📏 Get View Sizes
elev_WH = get_view_paper_size(elev)
cros_WH = get_view_paper_size(cros)
plan_WH = get_view_paper_size(plan)

Calculate Views Position

I will make a simple calculation by using Titleblock's Width and Height to find 3 points in the middle of the sheet. I won't make any checks to see if my views are bigger that the sheet, because I know that they will fit.

But you might want to do that in your future scripts if necessary 😉

# ⏺️ Define Viewport positions
seg = -tb_W/4
pt_elev = XYZ(seg  , tb_H/2, 0 )
pt_cros = XYZ(seg*2, tb_H/2, 0)
pt_plan = XYZ(seg*3, tb_H/2, 0)
Final Code

Lastly, let's place our views to these new calculated positions.

💡Keep in mind that the center of the Outline is used for placing your views!


#📰 Get TitleBlocks
default_title_block_id = doc.GetDefaultFamilyTypeId(ElementId(BuiltInCategory.OST_TitleBlocks))

#🔓 Start Transaction
t= Transaction(doc, 'Create New Sheet')
t.Start() #🔓

#📰 Create Sheet
new_sheet = ViewSheet.Create(doc, default_title_block_id)
print('Created New Sheet: {} - {}'.format(new_sheet.SheetNumber, new_sheet.Name))

#👉 Get Views
elev = doc.GetElement(ElementId(395398))
cros = doc.GetElement(ElementId(395407))
plan = doc.GetElement(ElementId(395416))


#📏 Get TitleBlock Size
titleblock = get_titleblocks_from_sheet(doc, new_sheet)
# titleblock_typ = doc.GetElement(titleblock.GetTypeId())
tb_W = titleblock.get_Parameter(BuiltInParameter.SHEET_WIDTH).AsDouble()  # In Feet
tb_H = titleblock.get_Parameter(BuiltInParameter.SHEET_HEIGHT).AsDouble() # In Feet


# ⏺️ Define Viewport positions
seg = -tb_W/4
pt_elev = XYZ(seg  , tb_H/2, 0 )
pt_cros = XYZ(seg*2, tb_H/2, 0)
pt_plan = XYZ(seg*3, tb_H/2, 0)


# 🖼️ Place Views on Sheets (Viewport.Create)
if Viewport.CanAddViewToSheet(doc, new_sheet.Id, elev.Id):
    viewport_elev = Viewport.Create(doc, new_sheet.Id, elev.Id, pt_elev)
    viewport_cros = Viewport.Create(doc, new_sheet.Id, cros.Id, pt_cros)
    viewport_plan = Viewport.Create(doc, new_sheet.Id, plan.Id, pt_plan)


t.Commit() #🔒

Here is the result:

Summary

Now you know how to work with Sheets!

If you want to get the Revit File or the complete code from the lesson, scroll all the way up and have a look in Download Files section.

Happy Coding!

HomeWork

Practice what we learnt in this lesson by creating a few sheets and placing views on them!

You can grab my Revit File and just follow along, or you can combine it with the previous lesson and create new floor plans for multiple levels, and place each one individually on the sheet.

⌨️ Happy Coding!

Questions:

Can I create sheets without a TitleBlock?

Can I create sheets without a TitleBlock?

I get error when reading CropBox from DraftingView.

I get error when reading CropBox from DraftingView.

DraftingView wasn't placed on a Sheet.

DraftingView wasn't placed on a Sheet.

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