If you are an experienced AutoCAD user, it is likely you have had some experience with creating or using customized scripts or routines to increase your productivity based on unique needs. You may have also seen such add-on programs attempt to offer similar object-like behavior as seen in Revit today (remember Auto-Architect by Softdesk?). Unlike the more generic platform of AutoCAD, Revit is a specific application designed for architecture and engineering. As such, Autodesk has slowly and deliberately opened up the application programming interface for customization by others. This chapter will help you understand how to create your own commands and applications to extend the power of Revit to meet your needs.
In this chapter, you'll learn to:
Understand the basics of the Revit API
Understand the new .addin
manifest file method used to load custom command and applications into Revit
Set up and build your own custom external applications and commands
All great applications provide a means to extend their functionality through some sort of API, and Autodesk Revit is no different. The software community's desire to customize and extend software applications to suit their specific needs has been around as long as software itself. The integration of an efficient means to accomplish application customization will usually have some effect on an application's popularity in the marketplace.
One of the most important reasons that a software company would provide a powerful application programming interface (API) in their product is to allow customers to purchase their product in a state that may not be exactly what is needed but is easily molded to suit their needs, resulting in a larger market share than if a useful API was not provided. The Revit API supports in-process dynamic link libraries (DLLs) only and runs on Microsoft's .NET Framework (www.microsoft.com/net
). Only single-threaded access is supported, but future releases of the API will likely support multithreading. Since the API runs on the .NET Framework, it supports all native .NET programming languages. Although most of the examples found online and in the Revit 2011 SDK are written in the C# language, Visual Basic .NET (VB.NET) is equally capable of providing the same functionality. The choice between .NET programming languages is purely up to the programmer.
There are two types of DLLs you can develop with the Revit API: external commands and external applications. The differences between the two types are distinct in both lifetime as well as scope.
External commands are accessible from the Add-ins tab under the External Tools drop-down button. External commands have access to the current document as well as any elements selected when the command is executed. Commands can be registered into Revit through the Revit.ini
file or by using an .addin
manifest file new to the Revit 2011 API. This new feature will be explained in greater detail later in this chapter in the section "External Utility Registration Options."
The memory lifetime for external commands lasts only from the time the command is clicked, to when the command returns success or failure. Any objects created or held in memory by the command are destroyed when the command is completed. Data will not persist in memory alone from one external command to another. If data persistence is needed from one external command to another, you will need to employ a means to store this data either by parameters or by an external database.
The IExternalCommand
interface is a required implementation for all Revit API commands and must accompany a public function named Execute
to access it at runtime. Empty boilerplate samples of this interface are shown here in both VB.NET (Listing 24.1) and C# (Listing 24.2).
Example 24.1. VB.NET IExternalCommand
Example
Imports Autodesk.Revit Imports Autodesk.Revit.Attributes Imports Autodesk.Revit.DB Imports Autodesk.Revit.UI <Regeneration(RegenerationOption.Manual)> _ <Transaction(TransactionMode.Automatic)> _ Public Class Command Implements IExternalCommand Public Function Execute(ByVal commandData As UI.ExternalCommandData, _ ByRef message As String, _ ByVal elements As DB.ElementSet) _ As UI.Result Implements IExternalCommand.Execute Try ' Command implementation Return Autodesk.Revit.UI.Result.Succeeded
Catch ex As Exception Return Autodesk.Revit.UI.Result.Failed End Try End Function End Class
Example 24.2. C# IExternalCommand
Example
using Autodesk.Revit; using Autodesk.Revit.Attributes; using Autodesk.Revit.DB; using Autodesk.Revit.UI; [Regeneration(RegenerationOption.Manual)] [Transaction(TransactionMode.Automatic)] public class Command : IExternalCommand { public Autodesk.Revit.IExternalCommand.Result Execute(Autodesk.Revit.ExternalCommandData commandData, ref string message, Autodesk.Revit.ElementSet elements) { // Command implementation } }
It is good practice to always trap the main functionality attempt within a try
statement. This provides a stable means of trapping any complications that may arise during the command startup:
Public Function Execute(ByVal commandData As ExternalCommandData, _ ByRef message As String, _ ByVal elements As DB.ElementSet) _ As Result Implements IExternalCommand.Execute Try ' Functionality extends from here Return Result.Succeeded Catch ex As Exception ' If something goes wrong, exit and return failed Return Result.Failed End Try End Function
Three return states exist for the IExternalCommand
interface. The only result that will allow changes to be committed to the model is IExternalCommand.Result.Succeeded
. All others will roll back any changes made by the command. The three return states are shown here:
Return IExternalCommand.Result.Succeeded Return IExternalCommand.Result.Failed Return IExternalCommand.Result.Canceled
CommandData
The CommandData
parameter provides access to both the application information and the model database information. Information for the current view is also accessible through this parameter.
Message
The Message
parameter serves as a means of reporting error information to the user upon returning a result of Failed. The Message
parameter will only be displayed if the command status returns Failed. There is a limit of 1,023 characters for this message; longer strings will be truncated.
Elements
The Elements
parameter provides access to objects currently selected while the command is executed.
External applications are loaded when Revit starts and remain loaded until Revit shuts down. One of the biggest advantages that applications have over commands is that applications provide access to Revit document events. Applications are typically accessible from the Add-Ins tab of the ribbon, but the 2011 API now also provides a means to add application ribbon items to the Analyze tab in Revit MEP 2011.
The IExternalApplication
interface is a required implementation for all Revit API external applications. External applications must accompany OnStartup
and OnShutdown
functions that fire off when Revit launches (OnStartup
) and when it closes (OnShutdown
). Empty boilerplate samples of this interface are shown here in both VB.NET (Listing 24.3) and C# (Listing 24.4).
Example 24.3. VB.NET IExternalApplication
Example
Imports Autodesk.Revit.UI Imports Autodesk.Revit.UI.Events Imports Autodesk.Revit.Attributes <Transaction(TransactionMode.Automatic)> _ <Regeneration(RegenerationOption.Manual)> _ Public Class App ' Required Interface...
Implements IExternalApplication Public Function OnStartup(ByVal a As UIControlledApplication) _ As Result Implements IExternalApplication.OnStartup ' OnStartup implementation End Function Public Function OnShutdown(ByVal a As UIControlledApplication) _ As Result Implements IExternalApplication.OnShutdown ' OnShutdown implementation End Function End Class
Example 24.4. C# IExternalApplication
Example
using Autodesk.Revit.UI; using Autodesk.Revit.UI.Events; using Autodesk.Revit.Attributes; namespace ClassLibrary1 { [Regeneration(RegenerationOption.Automatic)] public class Application : IExternalApplication { Public Result OnStartup(ControlledApplication application) { // OnStartup implementation } Public Result OnShutdown(ControlledApplication application) { // OnShutdown implementation } } }
There are now two options for registering external commands and applications into the Autodesk Revit user interface.
The Revit API now offers the ability to register API applications into Revit using an .addin
manifest file. Manifest files will be read automatically by Revit when they are placed in one of two locations on a user's system:
Windows XP
C:Documents and SettingsAll UsersApplication DataAutodeskRevitAddins2011
C:Documents and Settings<user>Application DataAutodeskRevitAddins2011
Windows 7
C:ProgramDataAutodeskRevitAddins2011
C:Users<user>AppDataRoamingAutodeskRevitAddins2011
All files named .addin
in these locations will be read and processed by Revit during startup. It is possible to load multiple elements within a single manifest by nesting multiple AddIn
elements into one .addin
file. An example .addin
file adding our example ExternalCommand
looks like this:
<?xml version="1.0" encoding="utf-8" standalone="no" ?> <RevitAddIns> <AddIn Type="Command"> <Assembly>C:Revit ProjectsExampleCommand.dll</Assembly> <AddInId>72d6cd76-5462-4c97-b56c-5468b08ba742</AddInId> <FullClassName>ExampleCommand.Command</FullClassName> <Text>Batch Family Export Utility</Text> <VisibilityMode>NotVisibleInFamily</VisibilityMode> <LongDescription>Batch export all family files as external RFA files. i Each file will be saved into a subdirectory named by its category i beneath the parent directory selected by the user.</LongDescription> <TooltipImage> C:Revit ProjectsPreview_BatchFamilyExport.PNG</TooltipImage> </AddIn> </RevitAddIns>
The example ExternalApplication.addin
file would look like this:
<?xml version="1.0" encoding="utf-8" standalone="no" ?> <RevitAddIns> <AddIn Type="Application"> <Name>ExampleApplication</Name> <Assembly>c:Revit Projects ExampleApplication.dll</Assembly> <AddInId>fb36c4b0-d4c2-4ead-96db-81f48407e57a</AddInId> <FullClassName>ExampleApplication.Application</FullClassName> </AddIn> </RevitAddIns>
The .addin
XML schema utilizes the XML tags described in Table 24.1.
Table 24.1. .addin
XML Schema
Description | |
---|---|
| The full path to the add-in assembly file. Required for all |
| The full name of the class that implements |
| A GUID that represents the ID of this particular application. |
| The name of application. Required; for |
| The name of the button. Optional; use this tag for |
| Short description of the command, will be used as the button tooltip. Optional; use this tag for |
| Provides the ability to specify if the command is visible in project documents, family documents, or no document at all. Also provides the ability to specify the discipline(s) where the command should be visible. Multiple values may be set for this option. Optional; use this tag for |
| The full name of the class in the assembly file that implemented |
| The path to the icon to use for the button in the External Tools drop-down menu. The icon should be 32 x 32 pixels for best results. Optional; use this tag for |
Long description of the command; will be used as part of the button's extended tooltip. This tooltip is shown when the mouse hovers over the command for a long amount of time. You can split the text of this option into multiple paragraphs by placing | |
| The path to an image file to show as a part of the button extended tooltip, shown when the mouse hovers over the command for a longer amount of time. Optional; use this tag for |
| Localization setting for |
In Revit 2011, the registration method utilizing the Revit.ini
file is still supported; however, it will not be supported in future releases of the API. This is because it does not offer any of the new capabilities of the .addin
manifest method. Be aware that updaters may not be registered from external applications registered using the Revit.ini
method because Dynamic Model Update registration requires a valid AddInId
.
Create a backup copy of your Revit.ini
found in the C:Program FilesAutodeskRevit Architecture 2011Program
directory. Only after you have backed up your Revit.ini
file, launch Notepad and open Revit.ini
for editing. Press Ctrl+F, and search within Notepad for the text string [ExternalCommands]
. If your Revit.ini
does not contain this entry, we will need to add it to the end of the file.
The [ExternalCommands]
portion of the Revit.ini
requires a line directly beneath it entered as ECCount=#
, where # would be replaced by the total number of external commands being loaded into the Revit.ini
file. If you have two external commands, the entry would read ECCount=2
.
[ExternalCommands] ECCount=1
Each externally defined command requires four lines to describe the name, the class, the assembly filename, and a description. Only the description line is optional.
In the following example external command entry, the number preceding the = represents the command's loading order into Revit. Commands must be numbered in sequence without any skipped numbers:
ECName1=Batch Family Export ECClassName1=BatchFamilyExporter.Command ECAssembly1=C:BatchFamilyExtractorinDebugBatchFamilyExporter.dll ECDescription1=Batch Family Export Utility...
In the previous code, the entry for ECName
will display in the Add-Ins tab of the ribbon beneath External Commands for your command.
ECClassName
requires the namespace of the project to be loaded followed by the class name containing the IExternalCommand
reference. The required format is NameSpace.Classname
. In this example, our namespace is BatchFamilyExporter
, and our class containing the IExternalCommand
reference is Command
.
ECAssembly
requires the full path and filename of the DLL file to be loaded containing the external command.
ECDescription
is optional and will display in the status bar of Revit.
Several development environment options are available for building and debugging .NET Revit API utilities. Although Visual Studio 2008 Professional is preferred, it is not free. Microsoft provides free express versions of Visual Studio 2008 in both VB.NET and C# on its website at www.microsoft.com/express
. These express versions provide more than enough functionality to generate a fully featured .NET Revit API utility.
A new .NET programming language known as F# is quickly emerging as the next generation of .NET technology. F# has been so successful that Microsoft has promoted it to a "first-class" language in Visual Studio 2010. See www.fsharp.net
for more information.
Debugging a Revit command or application in Visual Studio 2008 requires that you first enter the full path and filename to your Revit.exe
in the Start External Program field in the Debug tab of the Project Properties dialog box, as shown in Figure 24.1. The Revit API does not yet provide a means to interact with the Revit application in an externally defined stand-alone executable file. Entering the full path and filename to a Revit file in the Command Line Arguments field will launch that file when the project enters debug mode.
The Revit 2011 SDK is available on the Revit 2011 installation disk and in the downloadable installation package; however, you can download the latest version of the SDK from Autodesk's website at www.autodesk.com/revit
. The SDK contains several working samples of code written in both the C# language as well as VB.NET. Examples range from simple element modifications to family creation. The "Getting Started with the Revit API" document included with the SDK will help you with the examples provided.
The Revit 2011 API requires the .NET Framework Version 3.5 as a prerequisite, but since this same framework version is required to install and run Revit in general, it is not necessary to build your applications with this as a runtime prerequisite. If you choose to build your application using a .NET Framework version newer than 3.5, you should build your application to verify the installation of your required framework version at runtime.
If you are planning on updating any existing Revit API utilities to run in Autodesk Revit 2011, be prepared to make some rather major changes to your code. If you have any familiarity with previous versions of the Revit API, you will immediately notice some differences as you explore the new version.
The changes introduced in this version are going to make Autodesk Revit much more powerful in the long run because it will become possible to accomplish things that you just couldn't do in previous versions of the API. Not only are there new features, but other features have been redesigned to improve performance, such as the way elements are accessed and iterated within loops.
There was just one lonely DLL in previous releases of the API representing the entire thing. There are now two DLLs, each with its own scope, in the Autodesk Revit API:
RevitAPI.dll
This DLL now contains only methods used to access the Autodesk Revit application, documents, elements, and parameters at the database level.
RevitAPIUI.dll
This is a new DLL and contains all API interfaces related to manipulation and customization of the Revit user interface. This DLL provides access to the Revit command and application interfaces required by all external command and applications to run inside Revit.
IExternalCommand
and External Command
–related interfaces
IExternalApplication
and related interfaces
Selection
RibbonPanel, RibbonItem
, and all Ribbon
subclasses
TaskDialogs
(new)
Access to the document- and application-level interfaces through both the RevitAPIUI.dll
and the RevitAPI.dll
is now provided through a split of the Document
and Application
classes.
Autodesk.Revit.UI.UIApplication
This class provides access to the UI-level interfaces for the Revit application, including the ability to add RibbonPanel
s and the ability to obtain the active document in the Revit user interface.
Autodesk.Revit.UI.UIDocument
This provides access to UI-level interfaces for the document, such as the contents of the selection and the ability to prompt the user to make selections and pick points.
Autodesk.Revit.ApplicationServices.Application
This provides access to all other application-level properties.
Autodesk.Revit.DB.Document
This new DB.Document
class provides access to the document-level properties. The ExternalCommandData
interface provides access to the UIApplication
object as well as the active document view. From the active UIApplication
object, you can obtain the active UI document as either a family or a project model. You can also construct the UIApplication
from the Application
object, and the UIDocument
from the database-level document at any time.
Transactions are a means to prevent corruption by only allowing changes to be made when all required methods and conditions succeed. "All or nothing" is a good way to describe a transaction. Transactions roll back any changes contained within the transaction when a requirement or method does not return successfully. Transactions are common in complex database programs and are critical for ensuring that a complex operation succeeds as intended.
The 2011 API now provides a much more comprehensive system for handling transactions for changes being made to the model. There are three main transaction classes included in the Revit 2011 API. The most notable is the ability to manage subtransactions. Each of the three transaction types listed here share these four common methods:
Start
Starts the context
Commit
Commits all changes to the document
RollBack
Discards all changes to the document
GetStatus
Returns the current status of a transaction object
The Commit
and RollBack
methods also return a status indicating whether or not the method was successful. Available status values for Commit
and RollBack
include the following:
Uninitialized
Started
Committed
RolledBack
Pending
In the methods mentioned here for Commit
and RollBack
, the Pending
method is set after an attempt to either submit or roll back a transaction object but because of failures, that process could not be completed yet and is waiting for the end user's response (in a modeless dialog). Once the failure processing is finished, the status will be automatically updated (to either Committed
or RolledBack
status).
In the Revit 2011 API, events no longer automatically open transactions. Because of this change, the document won't be modified during an event unless one of the event's handlers modifies it by making changes within a transaction. If your application requires changes to anything in the document, these transactions are no longer optional.
All transactions must be named, and only one can be active at any given time. Transactions cannot be nested within other transactions. Each transaction will be represented as one "undo" in Revit upon successful completion. A series of three successful transactions would be represented as three individual undos in Revit, and so on.
Subtransactions are optional and can be used to enclose a set of Revit model modification commands. Subtransactions are a convenience feature that allows a developer to break up more complex tasks into logical, smaller, and more manageable tasks.
Subtransactions can only be created within an already opened transaction and must be closed (either committed or rolled back) before the transaction is closed (committed or rolled back).
Unlike transactions, a subtransaction may be nested, but any nested subtransaction must be closed before the enclosing subtransaction is closed. Subtransactions do not have names, nor do they appear as actions in the Undo command in Revit.
Transaction groups are optional and allow several independent transactions to be treated as a single transaction all at once. A transaction group can only be started when no transactions have been opened yet, and can be closed only after all enclosed transactions are closed (rolled back or committed). Transaction groups can be nested, but any nested group must be closed before the enclosing group is closed.
When a transaction group is to be closed, it can be rolled back, which means that all already submitted transactions will be rolled back at once. If not rolled back, a group can be either submitted or assimilated. In the former case, all submitted transactions (within the group) will be left as they were. In the later case, transactions within the group will be merged together into one single transaction that will bear the group's name.
To help improve the speed of a custom application or command as it interacts with the elements in a project, new regeneration options are available within the Revit API. In previous versions, all actions on model elements would force the model to update or regenerate automatically. Within the 2011 API, you can now specify automatic or manual regeneration. There is no default for this option and you must apply it to legacy application classes to allow your application to function in Revit 2011.
The new Autodesk.Revit.Attributes.RegenerationAttribute
custom attribute should be applied to your implementation class of the IExternalCommand
interface and IExternalApplication
interface to control the regeneration behavior for the external command and external application. There are two supported values:
RegenerationOption.Automatic
This option is the equivalent behavior with Revit 2010 and earlier versions of the Revit API. This option will regenerate after every model-level change. The performance of multiple modifications within the same file will be slower than RegenerationOption.Manual
. This mode is provided for behavioral equivalence with Revit 2010 and earlier; it is obsolete and will be removed in a future release.
RegenerationOption.Manual
This is the preferred option and will not regenerate after every model-level change. Instead, you may use the regeneration APIs to force an update of the document after a group of changes. Because this mode suspends all updates to the document, your application should not read data from the document after it has been modified until the document has been regenerated, or it runs the risk of accessing outdated information. This will eventually be the only regeneration option mode in future releases of the Revit API.
In the empty boilerplate samples shown here, you will see the definition of the manual option in VB.NET (Listing 24.5) and the automatic option in C# (Listing 24.6).
Example 24.5. VB.NET External Application Example
Imports Autodesk.Revit Imports Autodesk.Revit.UI Imports Autodesk.Revit.ApplicationServices <Regeneration(RegenerationOption.Manual)> _ Public Class Application Implements IExternalApplication
Public Function OnStartup(ByVal a As UIControlledApplication) _ As Result Implements IExternalApplication.OnStartup Try ' Application starts here Return Result.Succeeded Catch ex As Exception Return Result.Failed End Try End Function Public Function OnShutdown(ByVal a As UIControlledApplication) _ As Result Implements IExternalApplication.OnShutdown Return Result.Succeeded End Function End Class
Example 24.6. C# External Application Example
using Autodesk.Revit; using Autodesk.Revit.UI; using Autodesk.Revit.ApplicationServices; [Regeneration(RegenerationOption.Automatic)] public class Application : IExternalApplication { public Autodesk.Revit.UI.Result OnStartup(ControlledApplication application) { // OnStartup implementation } public Autodesk.Revit.UI.Result OnShutdown(ControlledApplication application) { // OnShutdown implementation } }
One of the more obvious changes made to the API in this release is the renaming of namespaces. These new names help improve consistency and make the API more suitable for future expansion. Most notable are the changes to the MEP namespaces to Mechanical, Electrical, and Plumbing. Figure 24.2 shows a small excerpt from the Revit 2011 API Namespace Remapping.xlsx
file included with the Revit 2011 SDK available for download from the Autodesk website.
This figure shows from left to right the class name, the previous 2010 namespace, the new 2011 namespace, and finally any notes explaining the change in the last column of the document. This spreadsheet will help get you on your way to understanding the namespace changes made to the Revit 2011 API.
Perhaps one of the most powerful improvements made in the Revit 2011 API has to do with the ribbon control additions. The ability to dock a custom text box into the Quick Access toolbar (QAT) for uses ranging from command entry with full argument syntax support to family search is now right at your fingertips. This opens doors for generating simpler customizations without having to create a clunky dialog interface.
The ability to create ribbon items with all the same features as the default ribbon items is a major improvement in this version of the API. You can now add long descriptions that display when you hover over a custom ribbon control as well as tooltip images. The list of ribbon improvements is quite extensive. For example, new controls that can be added to the ribbon in Revit 2011 include the following:
Creates a pull-down button with a default push button attached.
Creates a set of ToggleButtons, where only one of the set can be active at a time.
Creates a pull-down containing a set of selectable items, which can be optionally grouped.
Creates an input field for users to enter text; an event has also been added to pass the contents when Enter is pressed.
Similar to a flyout panel that slides down from a ribbon panel when clicked. These panels can contain any of the standard ribbon components.
Custom panels can also be added to the Analyze tab in Revit MEP 2011 as well as the Add-Ins tab via a new overload of Application.CreateRibbonPanel()
.
As a result of the ribbon control enhancements, some preexisting ribbon methods have been modified, as shown in Table 24.2.
Table 24.2. Changes in Ribbon Control Methods
Method/Property | Description |
---|---|
| Replaced with |
| Replaced with |
Property | Replaced with method |
Property | Replaced with method |
Method | Removed; use |
Method | Removed; use |
Method | Removed; use |
Method | Removed; use |
Now that you have a basic understanding of the Revit API, let's build a custom command and a custom application to call it. The code samples for the external command and external application are written in VB.NET using Visual Studio 2008.
The sample command will iterate through all family symbols in the model and export them to individual RFA files to a user-selected directory location. Each family file will be saved into a subdirectory named by its Revit family category name. The sample application will generate a custom ribbon button you will use to launch the custom external command. Harvesting content from existing projects will never be easier.
All external commands and applications are now required to have unique globally unique identifiers (GUIDs) in order to load into the Revit user interface. This means that each command and application is better managed as individual Visual Studio projects.
Visual Studio allows multiple stand-alone projects to be loaded into a master project, allowing you to easily manage multiple projects written in any supported .NET language in one convenient master environment.
This example demonstrates how to configure the ExternalCommand
as one Visual Studio project and the ExternalApplication
as another. You will then create a third master project into which you will load the two subprojects.
Create a new Visual Basic Class Library project in Visual Studio 2008. Make sure that the .NET Framework is set to 3.5. Name it ExampleCommand (Figure 24.3) and click OK.
Save the ExampleCommand project to disk and create another project using the same settings, but this time name it ExampleApplication and save it alongside the ExampleCommand project.
Close both of the command and application projects and create a third project. This time select the Empty Project template and name the solution ExampleProject, as shown in Figure 24.4.
Add the ExampleCommand and ExampleApplication projects to this new ExampleProject project by selecting File
All Autodesk Revit 2011 IExternalCommand
s and IExternalApplication
s now require two namespace references: RevitAPI.dll
and RevitAPIUI.dll
.
Right-click the title of the Visual Studio 2008 project name in the Solution Explorer and select Add
The ExternalApplication project requires two additional references to Presentation Core and Windows Base for the ribbon image functionality. Both of these references are available from the .NET tab of the Add References dialog box.
With the namespace references out of the way, the next step in our project configuration is to name the default IExternalApplication
and IExternalCommand
interface classes to something logical. The names of these classes can be anything you want but we recommend that a consistent naming strategy be used to make it easier to manage. We typically use the name of Command
for commands and App
for applications.
Select Class1.vb
in the ExternalCommand
project, and change its name to Command.vb
in the Visual Studio properties window. This class will serve as the main external command class containing the IExternalCommand
interface necessary to gain access to the Revit user interface.
The command class requires three namespace imports. The Revit
and Revit.UI
namespaces are used to get at the main program and user interface functionalities. The Revit.Attributes
namespace is used to access the regeneration and transaction features that are now required by the Revit 2011 API.
Imports Autodesk.Revit Imports Autodesk.Revit.UI Imports Autodesk.Revit.Attributes
The Transaction
and Regeneration
attributes are now required for all Revit 2011 API commands and applications. These two attributes must be called immediately before the class declaration and after any namespace imports.
The transaction serves as a container of changes that can be rolled back if a failure occurs. The regeneration options can now be set to manual, preventing Revit from regenerating on each and every modification made by your utility. This basically opens the door for speed and efficiency in your functions.
<Transaction(TransactionMode.Automatic)> _
<Regeneration(RegenerationOption.Manual)> _ Public Class Command .......
Listing 24.7 shows the completed Command
class code.
Example 24.7. The Completed Command
Class
Imports Autodesk.Revit Imports Autodesk.Revit.UI Imports Autodesk.Revit.Attributes ''' <summary> ''' Demonstrates how an ExternalCommand is added to the Revit user interface. ''' </summary> <Transaction(TransactionMode.Automatic)> _ <Regeneration(RegenerationOption.Manual)> _ Public Class Command Implements IExternalCommand ' All ExternalCommands i must implement IExternalCommand ' Handy way to post build version info to a form Public Const appDate As String = "(YYYY-MM-DD) - " Public Const appVer As String = "V#.#" Public Function Execute(ByVal commandData As ExternalCommandData, _ ByRef message As String, _ ByVal elements As DB.ElementSet) _ As Result Implements IExternalCommand.Execute Try Dim rvtApp As UI.UIApplication = commandData.Application Dim rvtDoc As DB.Document = rvtApp.ActiveUIDocument.Document Dim oForm As New formMain Dim sAppVersion As String = rvtDoc.Application.VersionNumber ' Pass the Revit document along with any other variables to the form oForm.Initialise(rvtApp, rvtDoc, appDate, appVer & i " for RAC" & sAppVersion) oForm.ShowDialog() ' ExecuteCommands must return either Succeeded, Failed, or Cancelled Return Result.Succeeded Catch ex As Exception ' If something goes wrong, exit and return failed message = "Command load failed" Return Result.Failed End Try End Function End Class
Select Class1.vb
in the ExternalApplication project and change its name to App.vb
in the Visual Studio properties window. This class will serve as the main external application class containing the IExternalApplication
interface for adding our ribbon panel to the Revit user interface.
The sample application class will add a pushbutton to the Add-Ins ribbon as well as a sample text box demonstrating how to capture the EnterPressed
event for this control. The text box control introduction to the Revit 2011 API is in our opinion the most useful addition to the UI namespace of the Revit 2011 API.
The sample external application needs six namespace imports to achieve the functionality needed as shown here. Revit.UI.Events
is required for the textbox EnterPressed
event example. Each of the System imports are required to add images to our ribbon controls.
Imports Autodesk.Revit.UI Imports Autodesk.Revit.UI.Events Imports Autodesk.Revit.Attributes Imports System.Windows.Forms Imports System.Windows.Media.Imaging Imports System.Reflection
Immediately following the namespace imports, we set the transaction mode to automatic. In an effort to make the program run faster, we then set the regeneration option to manual, as shown here, in an effort to make the program run faster.
<Transaction(TransactionMode.Automatic)> _ <Regeneration(RegenerationOption.Manual)> _ Public Class App ......
Listing 24.8 shows the completed App
class code.
Example 24.8. The Completed App
Class
Imports Autodesk.Revit.UI Imports Autodesk.Revit.UI.Events Imports Autodesk.Revit.Attributes Imports System.Windows.Forms Imports System.Windows.Media.Imaging Imports System.Reflection ''' <summary> ''' Demonstrates how an ExternalApplication is added to the Revit user interface. ''' </summary> <Transaction(TransactionMode.Automatic)> _ <Regeneration(RegenerationOption.Manual)> _
Public Class App ' Required Interface... Implements IExternalApplication ' Fired off by OnStartup Public Sub AddRibbonPanel(ByVal a As UIControlledApplication) ' Share the execution path with supplemental command paths Dim myPath As String = Assembly.GetExecutingAssembly.Location.Substring( _ 0, (Assembly.GetExecutingAssembly.Location.LastIndexOf("") + 1)) Dim rvtPanel As RibbonPanel = a.CreateRibbonPanel("Samples") ' Use the 'New' method to create pushbuttons Dim rvtData As New PushButtonData("ExampleCommand", _ "Batch Family Export Utility", _ myPath & "ExampleCommand.dll", _ "ExampleCommand.Command") rvtData.ToolTip = "Example Batch Family Export Command" ' Add the pushbutton to the ribbon panel Dim myButton As PushButton = rvtPanel.AddItem(rvtData) ' Add a textbox that will return its entered value when enter is pressed Dim txtData As New TextBoxData("MyTextBox") ' Add the textbox to the ribbon panel Dim txtBox As Autodesk.Revit.UI.TextBox = rvtPanel.AddItem(txtData) txtBox.Value = "Textbox Event Example" txtBox.Image = New BitmapImage(New Uri(myPath & "enter.png")) txtBox.ToolTip = "A sample message dialog displaying the entered data..." txtBox.ShowImageAsButton = True txtBox.PromptText = "Enter Text and Press Enter" ' This passes the event data to our event handler function AddHandler txtBox.EnterPressed, _ New EventHandler(Of TextBoxEnterPressedEventArgs)i (AddressOf Me.TextBoxPopup) End Sub Public Sub TextBoxPopup(ByVal sender As Object, _ ByVal args As TextBoxEnterPressedEventArgs) ' This reacts with the string entered into the textbox... MessageBox.Show(sender.value, _ "Textbox Value!", _ MessageBoxButtons.OK, _ MessageBoxIcon.Exclamation) End Sub Public Function OnStartup(ByVal a As UIControlledApplication) _ As Result Implements IExternalApplication.OnStartup Try ' Add the ribbon part to the Add-Ins tab AddRibbonPanel(a)
Return Result.Succeeded Catch ex As Exception Return Result.Failed End Try End Function Public Function OnShutdown(ByVal a As UIControlledApplication) _ As Result Implements IExternalApplication.OnShutdown Return Result.Succeeded End Function End Class
The sample command will need a user form as a means to interact with the user. Right-click ExternalCommand in the Solution Explorer and select Add
The next step is the design of the user form along with all required controls necessary to provide the functionality we need. Save the project and open the design view of the formMain
class. Figure 24.6 illustrates what the completed form design should look like.
You can be creative as to your placement of these controls so long as they are of the correct control types and match the names listed here. The controls are listed in order from top to bottom, going left to right, as illustrated in the previous example.
Add a FolderBrowserDialog from the toolbox to the form named FolderBrowserDialog1. This will serve as a helper dialog box to navigate and select an alternate directory for the RFA file family exports. This control only displays in the form design tray and will not necessarily display on the form itself.
Provides progress information to user. Place this control along the top of the form as illustrated earlier.
The example form design shows the LabelExport control placed underneath the left corner of the progress bar along the top of the form. This label displays as the main prompt to the user on load.
Place this control beneath the progress bar and set its text to .... This button will be used to launch the folder browse dialog box. Double-click the ButtonBrowse control and enter the following code:
Private Sub ButtonBrowse_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles ButtonBrowse.Click ' Browse to select the parent export path Me.FolderBrowserDialog1.ShowDialog() If Me.FolderBrowserDialog1.SelectedPath.ToString() <> "" Then Me.LabelExportPath.Text = Me.FolderBrowserDialog1.SelectedPath.ToString() End If End Sub
Place a label control named LabelExportPath to the right of the ButtonBrowse control. This label will display the path to the selected export directory.
Place a label control beneath the ButtonBrowse button named LabelExport. This label will display the current family file as it is being processed by the main export functions.
The bottom-left button, named ButtonExport, is used to launch the export command and commit the start of the export process. Set the text for this button to Export all Families. Double-click this button control to create the basic code structure for the click event. The code behind this button is shown in "The Main Export Function" section later in this chapter.
The bottom-right button, named ButtonCancel, is used to close the command without performing any actions at all. Set the text for this control to Cancel. Double-click the Cancel button and enter the following code:
Private Sub ButtonCancel_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles ButtonCancel.Click Me.Close() End Sub
The following lines are used to make the Revit application, current Revit project model, and default export path available to all functions within the form class. These declarations will go just beneath the form class declaration:
Private rvtDir As String = "" Private rvtApp As UI.UIApplication = Nothing Private rvtDoc As DB.Document
The command example is quite simple in function but still requires a few supplemental helper functions:
It is common to utilize an initialization subroutine within a form as a means of introducing any required parameters to the form as it is being launched. This provides an easy way to pass object variables to the form that can then be used within the form class.
Among the objects that we are interested in passing over to the user form are the Revit application object, current Revit project model, a string representing the build date, and another string representing an arbitrary build version, such as V1.0.
This function will also set our default export path to the directory of our model using the central model file path if the project is workshared.
Public Sub Initialise(ByVal app As UI.UIApplication, _ ByVal document As DB.Document, _ ByVal myAppDate As String, _ ByVal strAppVer As String) ' Hide the progressbar until we need it Me.ProgressBar1.Visible = False ' Form title with version Me.Text = "Batch Family Exporter - " & myAppDate & strAppVer ' Expose Revit doc and app rvtDoc = document rvtApp = app ' Set default export path adjacent to model location ' If workshared, use the central model path If rvtDoc.IsWorkshared = True Then Try rvtDir = Path.GetDirectoryName i (rvtDoc.WorksharingCentralFilename) _ & "/Exported Families/" Catch ex As Exception ' Detached model will not have a file path End Try Else rvtDir = Path.GetDirectoryName(rvtDoc.PathName) & _ "/Exported Families/"
End If ' The proper formatted file path... Me.LabelExportPath.Text = Replace(rvtDir, "/", "") End Sub
CheckValidFileName
While Revit provides its own means of preventing invalid file-naming characters from making their way into family names, you can use this function as a precautionary means. This filename validity snippet is useful anytime you are exporting files and need to verify that the characters used in the filename are valid.
Public Shared Function CheckValidFileName (ByVal fileName As String) As String CheckValidFileName = fileName For Each c In Path.GetInvalidFileNameChars() If fileName.Contains(c) Then ' Invalid filename characters detected... ' Could either replace characters or return empty CheckValidFileName = "" End If Next Return CheckValidFileName End Function
The main export function in the sample project is all nested inside the ButtonExport_Click
subroutine. There will be a lot going on inside this routine. The meat of the function is where the family symbols are filtered out of the Revit document and then iterated one by one and exported out as external RFA family files into a subdirectory named using the family's Revit category name.
The Revit 2011 API provides a much more efficient and robust means of filtering elements for selection as well as iterating elements within these collections.
' Filter to get a set of elements that are elementType Dim filter As New DB.ElementIsElementTypeFilter Dim collector As New DB.FilteredElementCollector(rvtDoc) collector.WherePasses(filter)
Once the elements have been gathered from the model using an element filter, we can iterate through them quite easily. This snippet illustrates the element iteration code:
Dim iter As IEnumerator = collector.GetElementIterator Dim element As DB.Element Dim FamInst As DB.Family = Nothing Dim famSymb As DB.FamilySymbol = Nothing Dim category As DB.Category Do While (iter.MoveNext()) element = iter.Current
If (Type Of element Is DB.FamilySymbol) Then category = element.Category If Not (category Is Nothing) Then Try ' Create the category subdirectory exportPath = rvtDir & category.Name & "/" Directory.CreateDirectory(Replace _ (exportPath, "/", "", , , CompareMethod.Text)) Catch ex As Exception ' Category subdirectory exists End Try Try ' family variable to Element famSymb = element FamInst = famSymb.Family sFamName = FamInst.Name ' Verify famname is valid filename and exists If Dir$(exportPath + sFamName & ".rfa") = "" And _ CheckValidFileName(sFamName) <> "" Then Me.LabelFileName.Text = "..." & _ category.Name & "" & _ FamInst.Name Dim famDoc As DB.Document = rvtDoc.EditFamily(FamInst) famDoc.SaveAs(exportPath + sFamName & ".rfa") famDoc.Close(False) End If Catch ex As Exception ' Prevent hault on system families End Try End If End If Loop
Listing 24.9 shows the completed formMain
code.
Example 24.9. The Completed formMain
Code
Imports Autodesk.Revit Imports System.IO Public Class formMain Private rvtDir As String = "" Private rvtApp As UI.UIApplication = Nothing Private rvtDoc As DB.Document Public Sub Initialise(ByVal app As UI.UIApplication, _ ByVal document As DB.Document, _ ByVal myAppDate As String, _ ByVal strAppVer As String)
' Hide the progressbar until we need it Me.ProgressBar1.Visible = False ' Form title with version Me.Text = "Batch Family Exporter - " & myAppDate & strAppVer ' Expose Revit doc and app rvtDoc = document rvtApp = app ' Set default export path adjacent to model location ' If workshared, use the central model path If rvtDoc.IsWorkshared = True Then Try rvtDir = Path.GetDirectoryName (rvtDoc.WorksharingCentralFilename) _ & "/Exported Families/" Catch ex As Exception ' Detached model will not have a file path End Try Else rvtDir = Path.GetDirectoryName(rvtDoc.PathName) & _ "/Exported Families/" End If ' The proper formatted file path... Me.LabelExportPath.Text = Replace(rvtDir, "/", "") End Sub Public Shared Function CheckValidFileName(ByVal fileName As String) As String CheckValidFileName = fileName For Each c In Path.GetInvalidFileNameChars() If fileName.Contains(c) Then ' Invalid filename characters detected... ' Could either replace characters or return empty CheckValidFileName = "" End If Next Return CheckValidFileName End Function Private Sub ButtonExport_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles ButtonExport.Click ' Return a qualified export path rvtDir = Replace(rvtDir, "", "/", , , CompareMethod.Text) Dim sFamName As String = "" Dim exportPath As String = "" Try ' If the parent export directory is missing, create it Directory.CreateDirectory _ (Replace(rvtDir, "/", "", , , CompareMethod.Text)) Catch ex As Exception
' Message to show any errors MsgBox(Err.Description, MsgBoxStyle.Information, Err.Source) End Try ' Filter to get a set of elements that are elementType Dim filter As New DB.ElementIsElementTypeFilter Dim collector As New DB.FilteredElementCollector(rvtDoc) collector.WherePasses(filter) ' Iterate the elements Dim iter As IEnumerator = collector.GetElementIterator ' Variables for element handling Dim element As DB.Element Dim FamInst As DB.Family = Nothing Dim famSymb As DB.FamilySymbol = Nothing Dim category As DB.Category ' Quickly count for progress bar Dim iCntFam As Integer = 0 Dim iCnt As Integer = 0 Do While (iter.MoveNext()) element = iter.Current If (TypeOf element Is DB.FamilySymbol) Then iCntFam += 1 End If Loop ' Reset for export process iter.Reset() ' Start the progressbar Me.ProgressBar1.Visible = True Me.ProgressBar1.Minimum = 0 Me.ProgressBar1.Maximum = iCntFam Me.ProgressBar1.Value = iCnt ' The export process Do While (iter.MoveNext()) element = iter.Current If (TypeOf element Is DB.FamilySymbol) Then Me.ProgressBar1.Value = Me.ProgressBar1.Value + 1 category = element.Category If Not (category Is Nothing) Then Try ' Create the category subdirectory exportPath = rvtDir & category.Name & "/" Directory.CreateDirectory(Replace _ (exportPath, "/", "", , , CompareMethod.Text)) Catch ex As Exception ' Category subdirectory exists End Try Try ' family variable to Element famSymb = element FamInst = famSymb.Family sFamName = FamInst.Name
' Verify famname is valid filename and exists If Dir$(exportPath + sFamName & ".rfa") = "" And _ CheckValidFileName(sFamName) <> "" Then Me.LabelFileName.Text = "..." & _ category.Name & "" & _ FamInst.Name Dim famDoc As DB.Document = rvtDoc.EditFamily(FamInst) famDoc.SaveAs(exportPath + sFamName & ".rfa") famDoc.Close(False) End If Catch ex As Exception ' Prevent hault on system families End Try End If End If Loop Me.Close() End Sub Private Sub ButtonCancel_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles ButtonCancel.Click Me.Close() End Sub Private Sub ButtonBrowse_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles ButtonBrowse.Click ' Browse to select the parent export path Me.FolderBrowserDialog1.ShowDialog() If Me.FolderBrowserDialog1.SelectedPath.ToString() <> "" Then Me.LabelExportPath.Text = Me.FolderBrowserDialog1.SelectedPath.ToString() End If End Sub End Class
If you are interested in learning more about the Revit API, here are some online resources for your reference:
Autodesk Developer Center and Developer Network (ADN): www.autodesk.com/adn
The Building Coder (a blog by Jeremy Tammik): thebuildingcoder.typepad.com
Autodesk Revit API Discussion Group: discussion.autodesk.com/forum.jspa?forumID=160
DevTV: Introduction to Revit API Programming: download.autodesk.com/media/adn/DevTV_Introduction_to_Revit_Programming_new
As you work in Revit, all of your actions are recorded in journal files. These files, which can be found in C:Program FilesAutodeskRevit Architecture 2011Journals
, are basic text files that can be opened and reviewed in any text editor such as Notepad or Notepad ++ (notepad-plus.sourceforge.net
). If you have ever had any kind of complicated support issue, you may have been asked to send your journal files to Autodesk for review. With the journal files, the Autodesk support team can replay the actions captured in an active Revit session. Journal files can also be used to perform some simple automated tasks.
One simple example of a journal script is the family upgrade utility, which is a simple journal-based script that will launch Revit and open a series of families you specify, thus upgrading them to the latest version. This utility is included with your Revit installation package and can be found after you extract the installation package in the subfolder: UtilitiesCommonContentBatchUtility
.
Before running the script, you must first copy the Upgrade_RFA.txt
and Upgrade_RFA.bat
files into the root directory of the library you want to upgrade and then run the Upgrade_RFA.bat
file to generate a list of files to upgrade. This BAT routine will create another text file named famlist_rfa.txt
; don't change the name of this file or move it. The Upgrade_RFA.txt
file is a Revit journal file. If you open the journal file, you will find the main function listed as a subroutine between the code Sub
and End Sub
, as shown in Listing 24.10.
Example 24.10. The Main Function in the Family Upgrade Journal Script
Sub upgrade(namepath, file) Jrn.Command "Menu", "Open an existing project , 57601 , ID_FILE_OPEN" Jrn.Data "File Name" _ , "IDOK", namepath Jrn.Command "Internal" , " , ID_REVIT_SAVE_AS_FAMILY"
Jrn.Data "File Name" , "IDOK", namepath Jrn.Command "Menu" , "Close the active project , ID_REVIT_FILE_CLOSE" End Sub
To execute the journal script, simply drag the Upgrade_RFA.txt
file onto the Revit desktop icon. Revit will launch and open each RFA file in the list. As you can see in Listing 24.10, the actual journal commands being passed to Revit are quite simple. ID FILE OPEN
opens the project, whereas ID REVIT SAVE AS FAMILY
saves it in the new version.
You can find another example of journal scripting in a local file utility developed in collaboration between David Baldacchino, David Kingham, and James Vandezande. This utility was created in an open source scripting language called AutoHotkey (www.autohotkey.com
). This language is easy to learn and can be an excellent learning platform for aspiring programmers. You can download the local file utility from the AUGI Forums at forums.augi.com/showthread.php?t=65897
.
The local file utility has several iterations for each of the developer's respective companies, but at its core, it performs basic file functions to create local copies of Revit central files and then automatically opens them with journal-based scripts. Let's take a look at that portion of the utility.
Other parts of the script will identify the central file and locations for a user's local copies. Once a local copy of the central file has been created, journal commands are generated and written to an actual journal file:
Set Jrn = CrsJournalScript Jrn.Command "Menu" , "Open an existing project , 57601 , ID_REVIT_FILE_OPEN" Jrn.Data "File Name" _ , "IDOK", "%DESTINATION%\%LOCALFILE%.rvt" Jrn.Data "WorksetConfig" _ , "Editable", 0 Jrn.Data "MessageBox" _ , "IDOK", "This Central File has been copied or moved from ""%DRV%\%DSCPLN%\%Folder%\%C1%"" to ""%DESTINATION%\%LOCALFILE%.rvt""." & vbCrLf & "" & vbCrLf & "If you wish this file to remain a Central File then you must re-save the file as a Central File. To do this select ""Save As"" from the ""File"" menu and check the ""Make this a Central File after save"" check box (under the options button) before you save." & vbCrLf & "" & vbCrLf & "If you do not save the file as a Central File then it will be considered a local user copy of the file belonging to user ""%USERNAME%""." Jrn.Command "Menu" , "Workset control , 33460 , ID_SETTINGS_PARTITIONS"
The journal file is then executed by passing it to the Revit program executable, which is similar to actually dropping a journal file on the Revit desktop icon in the family upgrade method.
Notice the syntax of the journal launching code is quite similar to running an application with optional parameters at the Windows command prompt. The following line runs Revit.exe
with the journal file (%JournalFile%
) in a maximized application window:
Run %AppPath%ProgramRevit.exe "%JournalFile%", Max
The need for some users and developers to extend the functionality of Revit is supported by the Revit application programming interface (API). Revit's software development kit (SDK) provides sample code and instructions for building add-ons to the application.
What are the two types of dynamic link libraries you can develop for Revit?
.addin
manifest file method used to load custom command and applications into Revit.The Revit API for 2011 now offers the ability to register API applications into Revit using an .addin
manifest file.
How is the .addin
manifest method different from previous versions?
You can start to create your own custom applications and commands for Revit using either Microsoft Visual Studio or a free tool such as Visual Studio Express or SharpDevelop.
How do you make the Revit API functions available in your developing environment?