Taking the Revit API to the Next Level as a Beginner Don Rudder –
CP1749 Learn how to simplify your life and get quicker results when using the Revit API to automate or extend the functionality of Revit. There is an obvious cost savings associated with automation, but there's an even larger ROI when even automation can be automated.
Learning Objectives At the end of this class, you will learn: •
Understand the power of a reusable code library
•
Plan, manage and maintain reusable code libraries
•
Use inline documentation
•
Use 5 Key Revit API Code Snippets to Save You Time and Money
About the Speaker Don Rudder is the director of software development and CTO for Case Design, Inc. He has extensive experience in .NET and web development for the AEC industry. His background, spanning over 15 years, is primarily in the MEP disciplines where he has served as designer, CAD manager, and customization consultant. He is a contributing author for the API chapters for the Mastering Revit Architecture 2011, Mastering Revit Architecture 2012, and Mastering Revit Architecture 2013 books. Also, look for my new book entitled “Autodesk Revit 2013 Customization with .NET How-to” available from PACKT Publishing. Don maintains a blog focusing on all things Revit and especially the API at www.RevitNet.blogspot.com.
Page 1 of 20
CP1749
Taking the Revit API to the Next Level as a Beginner 2012-11-09
case Free Monthly Add-Ins If you like what you see in this class, be sure to check out our series of free tools where a new tool is added every month. You can install these tools from our Add-In Manager: http://apps.case-inc.com/content/add-manager
Assumptions This class assumes that you are somewhat of a beginner programmer already learning to use the Revit API and have done some programming of your own of some sort. The Revit code samples will be only casually discussed with most of the emphasis on core concepts for managing and organizing existing code for the purpose of reusing it on multiple external code projects.
Introduction Reusable code libraries used as a means to improve software development efficiencies sound like such an obvious concept. The steps required to plan and build such a library, however, is a bit more challenging. This class will talk through these ideas and offer plenty of useful code samples along the way to get your code library started so you can begin to improve your own efficiencies as you continue to develop tools for Revit.
Software Development by Design Professionals While general scripting knowledge is becoming more and more common among design professionals, complex programming is still a bit rare. Most designers and BIM managers that program are often self-taught and rarely get introduced to advanced software development concepts. Most of the concepts covered in this class would be considered somewhat beginner to a seasoned programmer, but to most design professionals hopefully fresh and beneficial.
2
CP1749
Taking the Revit API to the Next Level as a Beginner 2012-11-09
Understand the Power of a Reusable Code Library When I mention reusable code libraries in this class, what I’m really talking about are standalone code projects (DLL files) with a series of custom public classes, functions and subroutines that can be called from external code projects. Reusable code is definitely one of the most valuable resources that a developer can produce for themselves. Beginner developers often overlook the power of structuring and maintaining these resources and find themselves constantly duplicating effort and hunting for code to copy and paste from various projects. Creating and managing a set of reusable code libraries is the key to improving your efficiency as you develop tools for any application. Perhaps one of the most beautiful features of the Microsoft .NET framework is that it allows functions and subroutines to be called between DLL files so long as the DLL files each exist in a common directory. Since there is no need to register .NET DLL files in order to call functions from them it becomes easier than ever to build complex software utilities whose functions span across various DLL file libraries.
Benefits of a Reusable Code Library In anything you do, having a healthy variety of tools to do your job will lead to you being more efficient. While the benefits that can be gained from a well-planned and maintained reusable code library are virtually limitless, here are three that really stand out: •
•
•
A great place to stockpile “borrowed” code that you have yet to implement on your own. We all find useful code snippets from blogs, SDK samples, or other coworkers and to borrow this code is perfectly ok and nothing to be ashamed of. Easier to maintain complex functionality between multiple add-in projects. This also comes in handy when Autodesk makes core API changes to existing classes Avoid duplication of effort by centralizing common custom classes and methods
3
CP1749
Taking the Revit API to the Next Level as a Beginner 2012-11-09
Plan, Manage and Maintain Reusable Code Libraries As you develop more and more tools for Revit, you will certainly notice yourself using several virtually identical methods from one add-in project to another. These commonly used functions and sub routines are perfect candidates to be ported over to a reusable library. Here are four important topics to consider when building your own library of reusable methods and objects. Time Spent
The first thing you want to avoid is spending too much time creating it. In an ideal situation, the time that you spend making a reusable library should be less than a third of the time that you will spend using it in other projects.
Ideal Amount of Time Spent Using the Library Creating the Library
Usefulness
The second thing to consider is what to include in your library and how useful it will be do its users (often just yourself). How often do you see the object or methods being used? If you don’t think it will be used very often, then your reason for including it should be because it solves a difficult problem in a very simple way. Always comment your library code to the fullest.
Object Naming
The third and probably the most important thing to consider is how you name all of your objects. This is critical since external projects will rely on these names after implementation, so renaming them later could get extremely messy. Assigning clear and unique class names will help prevent clashes with classes from other libraries that you might use in your code. An example of this would be to not name a class in your master library “Rooms.” The default Revit API already has a class named Rooms and might cause you 4
CP1749
Taking the Revit API to the Next Level as a Beginner 2012-11-09
or someone else using your library a bit of confusion when using this class. Always avoid abbreviations in your method and object names whenever possible and have a clear goal in mind for every method or property that you intent to include in your library prior to committing any time to develop the final code. The more concise and specific the command is the more likely and common that it will get used. I suggest that you review all object, property, and method names with someone and have them guess solely based on their names what they will do to help you decide on any final names. If this person guesses something way off of what you were planning for the item to actually do you should consider a different and more obvious name. “There are only two hard problems in computer science: cache-invalidation and naming things.” -- Phil Karlton Method Arguments
The fourth consideration is probably the most difficult to plan, but should be taken very seriously. The quantity and object types of each of your method parameters should be planned to allow for ultimate flexibility. Use Optional arguments where it makes sense to make them easier to call in simpler situations.
Error Handling
Since these functions and subroutines will be called from possibly numerous projects, you should take special care to account for any kind of error. The key to any well written generalized method is its ability to accurately react to failures and prevent any sort of meltdown in your primary application.
AU_Settings
Helper Class Management
AU_Rooms AU_Views etc.
A helper class is a custom object that contains properties and methods that either extend the functionality for or simplify the use of another object existing in a library that is not editable in your current programming environment (Revit API). Our sample code library is made up of several of these helper classes. In our sample AU library, a master settings class named AU_Settings provides access via public properties to collections of our custom helper classes as well as core Revit document and application objects.
5
CP1749
Taking the Revit API to the Next Level as a Beginner 2012-11-09
Referencing an External Code Library into your Code Project There are two ways that you can reference a code library into your project. You should be familiar with the first way since this is how you get the Revit API into your project to begin with and is by referencing the read-only DLL. The other is as an editable project and provides much greater flexibility. Read-Only DLL
This is the most common means of referencing an external library. This method will expose the included namespaces and public functionalities into your active project but the included functions will not be editable. Read-Only DLL references can be added by either browsing or adding from the registered .NET or COM tabs of your references dialog in Visual Studio.
Editable Project
If you are using Visual Studio Express, then the programming language will need to be the same between your two projects, but with Visual Studio Professional or higher they can be any .NET supported language that you want. This preferred method requires that you have the source code project files for the library that you want to reference. First add the external library project to your current project by selecting File Add Existing Project from the drop down menu of Visual Studio. Navigate to either the .csproj or .vbproj file depending on the
6
CP1749
Taking the Revit API to the Next Level as a Beginner 2012-11-09
language of the project. The next step is to add a reference to it. This is what will make the methods available to your project. A reference will only become available in the Projects tab of your Add Reference dialog after it has been added as a project to your solution. Leave the Copy Local value for this library to the default of True.
7
CP1749
Taking the Revit API to the Next Level as a Beginner 2012-11-09
Inline Documentation Keeping up with what each of your custom methods and properties do as well as each and every method argument can be a tricky task. Fortunately the .NET framework has a simple, yet powerful built-in feature designed to help you document your custom objects and methods right inside your code. If you’ve ever needed good reason to beef up your code commenting habits, this is the one. Inline documentation allows a programmer to see within the intellisense information a description for each object, method, and argument and is an amazing feature when used appropriately. The next two images below demonstrates the simplified technique of getting the inline documentation framework automatically populated using just three commenting prefix characters immediately above the qualifying object, property, or method that you want to document (apostrophes for VB.NET and forward slashes for C#). The area highlighted in yellow is the automatically populated code generated after the third commenting character is entered.
Use the TAB key to skip forward to the next inline documentation block from within the object that you are currently documenting, SHIFT + TAB to go backwards
8
CP1749
Taking the Revit API to the Next Level as a Beginner 2012-11-09
The image below illustrates inline documentation for a property named ActiveDoc. Information entered into the summary tag will be shown in the intellisense description as the object is used in Visual Studio.
The information recorded in the inline documentations should be thorough enough for the programmer consuming it in their code can understand exactly what the objects are and how to use them yet brief enough that they will actually take the time to read it. Comments should make your code easier to understand and maintain, so keep your comments quick and to the point!
9
CP1749
Taking the Revit API to the Next Level as a Beginner 2012-11-09
Viewing Library Objects in the Object Browser The Object Browser is accessible from the main menu under View Object Browser and is a great tool for exploring external libraries. It should be noted that you will only be able to see objects and methods that are declared as public. Private methods and properties will not display in the object browser. Another thing to be aware of is that the order each of these items is listed in the object browser will be the same as the order in which they are listed in your code. If you prefer to have all of your properties listed first, you will need to list them first in your code.
10
CP1749
Taking the Revit API to the Next Level as a Beginner 2012-11-09
5 Key Revit API Code Snippets to Save Time and Money Did I say snippets? What I meant to say was complete tools! Now that we’ve learned the basics about planning and managing an external code library, let’s get into some code. The five tools that I’ve selected for this class are listed in the diagram below. We’ll utilize a series of helper classes managed within our sample library project to work with the data.
loading content rooms
plan views element filtering shared parameters
The image to the right shows the completed project solution with the commands and library projects loaded together. The library project contains our helper classes serving the reusable objects while the commands project contains the various classes implementing IExternaCommandData along with each of the user forms where we interact with the add-ins. I prefixed each of the library helper classes with AU_ as a means to make them easier to identify in the sample code as I use them throughout the solution.
11
CP1749
Taking the Revit API to the Next Level as a Beginner 2012-11-09
Sample #1 - Element Filtering Probably the most common task you will repeat as you develop add-ins for Revit is request elements based on some sort of filtering criteria. This sample does exactly and only this. Our AU_Settings class library contains a function named GetElementsByCategory that returns a list of elements matching the choices the user has made in the user form. '''
''' Get Elements by Category ''' '''
True to include Type Elements '''
True to include Instance Elements '''
Optional Category to Filter By '''
Element Name, matches if contained '''
A list of elements matching the requested category name '''
Public Function GetElementsByCategory(includeTypes As Boolean, includeInstances As Boolean, cat As Category, nameFilter As String) As List(Of Element) Try ' Collector Using col As New FilteredElementCollector(ActiveDoc) ' Include the Instance Elements? If includeInstances = True Then col.WhereElementIsNotElementType() End If ' Include the Type Elements? If includeTypes = True Then col.WhereElementIsElementType() End If ' Category Name? If Not cat Is Nothing Then col.OfCategory(cat.Id.IntegerValue) End If ' Name? If Not String.IsNullOrEmpty(nameFilter) Then ' LINQ List Dim m_eList As IEnumerable(Of Element) m_eList = col.ToElements ' LINQ Selection by Name Dim m_linq = From e In m_eList Where e.Name.ToLower.Contains(nameFilter.ToLower) ' Return the Values Return m_linq.ToList Else ' Return the List Return col.ToElements End If End Using Catch ex As Exception ' Failure Return Nothing End Try End Function
12
CP1749
Taking the Revit API to the Next Level as a Beginner 2012-11-09
Upon launching the add-in, all categories are retrieved from the model and bound into the category drop-down as real Category elements. The name for each Category is displayed in the control by setting the value for the combo box property for DisplayMember to “Name.” The user form only allows instance or type elements to be displayed, but the reusable function surely supports retrieving a list of both instance and type elements together.
The way it works is the user will first select a category that they want to filter the elements by. They will then select to filter for either instance or type elements using the radio buttons. They can optionally enter a value in the Name Contains text box to filter by the element name (our function uses LINQ to return the matching elements by their name). Clicking the Refresh button will display the results in the grid at the bottom of the form.
13
CP1749
Taking the Revit API to the Next Level as a Beginner 2012-11-09
Sample #2 - Load Content from Files I find myself needing to load content from external files quite often in my add-ins so I definitely keep this one in my library. If you ever attempt to load a family into a model that already exists, you have to tell Revit how to treat existing parameters and their values. I added a class named AU_Content to our library (not shown here) that implements IFamilyLoadOptions to make this a little easier to deal with. Once we have this class in our library, we can add any family from file we want by utilizing our super simple function named LoadFamilyFile. '''
''' Load a Family File into the Model ''' '''
Full Family File Path '''
True to Overwrite Parameters ''' on Existing Family Load '''
True on Success '''
Public Function LoadFamilyFile(filePath As String, overwriteParams As Boolean) As Family ' Return Family Dim m_f As Family = Nothing ' Check if File Exists If File.Exists(filePath) = False Then Return Nothing Try ' Load the Family ActiveDoc.LoadFamily(filePath, New AU_Content(overwriteParams), m_f) Catch End Try ' Return Value Return m_f End Function
Using the sample is about as simple as it gets. Upon calling the command, the user will be prompted to select one or more family files. A check box is provided in the lower left corner of the form to overwrite existing parameter values. Clicking the Load List button will load the families into the model.
14
CP1749
Taking the Revit API to the Next Level as a Beginner 2012-11-09
Sample #3 - Plan Views The plan view creation API is new in Revit 2013 AND requires three input arguments. The Level, ViewFamilyType, and finally a Document to create it in are all required arguments. Getting the list of Level and ViewFamilyType elements into our user form controls is handled by a pair of methods named GetViewFamilyTypes and GetPlanViewsByLevel in our AU_Settings class. Using the SelectedIndexChanged event of our ComboBoxViewFamily control, we display all views in the model that match the selected ViewFamilyType. I’ve added a helper class to the library named AU_PlanView to simplify the process of working with ViewPlan elements. It contains a few basic properties to make them easier to display listings for in a DataGrid view. Constructing the helper from an existing ViewPlan element will hold a reference to the existing element and expose the provided properties for it.
15
CP1749
Taking the Revit API to the Next Level as a Beginner 2012-11-09
Constructing this helper class with a view name, ViewFamilyType, and a Level will generate a new ViewPlan and then also exposing the provided properties as shown in the code below. '''
''' Create a New Plan View ''' '''
Name for the New View '''
ViewFamilyType to generate the view from '''
Level to generate the view for '''
Public Sub New(vName As String, vFamilyType As ViewFamilyType, vLevel As Level) ' Final Plan Element Dim m_vp As ViewPlan = Nothing Try Try ' Create the New View m_vp = ViewPlan.Create(vFamilyType.Document, vFamilyType.Id, vLevel.Id) ' View Name m_vp.Name = vName Catch End Try Catch ex As Exception MsgBox(ex.Message, MsgBoxStyle.Critical) End Try ' Return the View ViewPlanElement = m_vp End Sub
Sample #4 – Rooms Rooms are a common element to deal with in my add-ins, and can be a bit tricky to work with depending on what exactly it is that I need to so. This sample is used to handle the creation and/or placement of rooms in three distinct ways.
16
CP1749
Taking the Revit API to the Next Level as a Beginner 2012-11-09
The first option is to create a new room and also place it somewhere in the model. This constructor takes the room name, number, UV point (X,Y), level, and an optional department string and will create the room as well as place it at the point you specify in the UV argument. The room tag also gets placed at this same UV location. The helper class constructor to create and place a room is shown below. '''
''' Create and Place a New Room ''' '''
The name for the room '''
The number for the room '''
The UV location to place the room at '''
The level to place the room on '''
The department name for the room (optional) '''
Public Sub New(roomName As String, roomNumber As String, insertionPoint As UV, roomLevel As Level, Optional roomDepartment As String = "") Try ' Create the Room RoomElement = roomLevel.Document.Create.NewRoom(roomLevel, insertionPoint) RoomElement.Name = roomName RoomElement.Number = roomNumber RoomElement.Parameter(BuiltInParameter.ROOM_DEPARTMENT).Set(roomDepartment) ' Get the Data GetData() Catch End Try End Sub
The second option is to add a room to the model and not place it anywhere. This constructor requires a document to add the room to, phase to create it on, room name, number, and again an optional department name. '''
''' Add a New Room to the Model, but do not Place it ''' '''
The document to add the room into '''
The phase to create the room into '''
The name for the room '''
The number for the room '''
The department name for the room (optional) '''
Public Sub New(d As Document, p As Phase, roomName As String, roomNumber As String, Optional roomDepartment As String = "") Try ' Create the Room RoomElement = d.Create.NewRoom(p) RoomElement.Name = roomName
17
CP1749
Taking the Revit API to the Next Level as a Beginner 2012-11-09
RoomElement.Number = roomNumber RoomElement.Parameter(BuiltInParameter.ROOM_DEPARTMENT).Set(roomDepartment) ' Get the Data GetData() Catch End Try End Sub
The third and last option provided is to place a room that already exists in the model. This constructor requires the arguments of the room to place, level to place it on, and an optional height offset for the volume (default is 8). '''
''' Place an Unplaced Room into the first available ''' plan circuit found in the Model ''' '''
The room element to be placed '''
The level to place the room on '''
The limit offset height for the room '''
Public Sub New(r As AU_Room, roomLevel As Level, Optional lOffset As Double = 8) Try ' PlanCircuit Dim m_pCirc As PlanCircuit = Nothing ' Get the plan topology for our level Dim m_pTopo As PlanTopology = roomLevel.Document.PlanTopology(roomLevel) ' Iterate circuits in this plan topology For Each x As PlanCircuit In m_pTopo.Circuits ' Get the first circuit we find If x IsNot Nothing Then m_pCirc = x Exit For End If Next ' Valid Circuit? If Not m_pCirc Is Nothing Then Try RoomElement = roomLevel.Document.Create.NewRoom(r.RoomElement, m_pCirc) RoomElement.LimitOffset = lOffset ' Get the Data GetData() Catch End Try Else MsgBox("Could Not Find an Available Enclosed Circuit to Place the Room Into!", MsgBoxStyle.Critical, "Room Not Placed!") End If Catch End Try End Sub
18
CP1749
Taking the Revit API to the Next Level as a Beginner 2012-11-09
Sample #5 - Shared Parameters (Case Free Add-In for November) This sample solves a definite weakness in the base Revit UI. Adding parameters into the model from a shared parameter file is laborious in that it only allows a single parameter at a time making this process highly prone to human error. This tool was released as the case Free Add-In for November and can be downloaded from our apps site (http://apps.case-inc.com/) where a new free tool is offered each and every month. This sample is an improved shared parameter loader for project models. It enables the user to browse parameters by their group association as well as data type (area, text, length, etc.). You can also load as many as you want at a time!
Super Shared Parameter Loader utilizes a helper class named AU_SharedParameters that provides each of the functions necessary to open & read a shared parameter files as well as bind the shared parameters to each of the categories the user selects. A couple public properties in our AU_Settings class are used to store the shared parameter data as well as the built-in parameter groups required to assign the parameters as we bind them to categories in the project model. These two properties are shown in the code below.
19
CP1749
Taking the Revit API to the Next Level as a Beginner 2012-11-09
A helper class named AU_BuiltinParameterGroup gets the human readable name for the BuiltinParameterGroup objects from a public property named DisplayName. A public property named ParameterGroups in our AU_Settings class holds the list of each available group so we can easily bind the list to a user control hosted by a user form.
20