Introduction to Revit Macros
BIM managers have tasks they perform on a regular basis, whether on a single Autodesk® Revit® model or on multiple Revit models. Many of these tasks are repetitive and can become boring after doing the same task over and over. Luckily, Revit has a solution to this with its integrated Macro system. There are many tasks that can be automated such as model setup, model cleanup, view/sheet creation and duplication, quality control checks, parameter validation, and much more.
Before proceeding any further, I want you to know that I’m not a professional developer and I have only been writing macros in Revit for a little over a year as of this writing. I learned how to create macros by trial and error. If I can learn this way, I believe just about anyone else can also. My process has been to identify a task to automate or a problem to solve. I then break down the steps to solve using Revit as normal (if possible) and then I set out to solve it in a similar way through a macro.
Revit macros use the API (Application Programming Interface) to control the Revit application and work with the objects in the Revit model. The macros can be written in several programming languages including C#, VB.NET, Ruby, and Python. This article will show how to get started writing macros for Revit in C#, but is also applicable for the other languages.
Figure 1: Sample Macro Manager dialog in Revit
Revit macros are created by opening the Macro Manager tool from the Manage tab. Macros can be created either in the application or in the model. I highly suggest creating the macros in the application tab so you can reuse the macros in any Revit model. If you store the macro inside the model, then it is only available to that Revit model. Storing it in the model will also give you a warning every time you open the model that it has macros stored in it. This can cause other users of the model to think it may contain a virus.
To get started, you first need to create a module to store the macros in. The module allows you to pick the programming language (C# in this case) to use and to create a name for the module. Think of modules as a binder of macros. You can have multiple binders (modules) that contain multiple macros. You can use these modules to separate the macros into similar types.
After creating a module, you can then create a macro that will allow you to write code that will interface with the Revit application and Revit model. While initially creating the macro all you need to do is supply a name for the macro. Macro and module names cannot contain spaces or special characters. Try to keep the names simple: “CreateMEPViews” or “CopySheets,” for example.
Once you create a module or macro, Revit opens a new window outside of Revit called SharpDevelop. SharpDevelop is the IDE (Integrated Development Environment) you will use to write the macro code. Modules start out with several lines of code to begin with that you need to be careful not to change.
Figure 2: SharpDevelop IDE with a new module started
Before you can start coding, you need to learn some general concepts around the .NET programming platform upon which the Revit API is built. (For more info about C# and .NET a free book is available at http://www.robmiles.com/c-yellow-book/)
.NET is a programming platform created by Microsoft that many programmers use to write applications such as Revit. You can think of .NET as a library full of objects (known as methods) that you can use to create your macros. There are methods for working with files and folders, databases, graphics, and so on. The .NET library of methods is very large. Revit also has a library of methods that allow you to interface with it through the API. These methods include almost everything available to you in the regular Revit application (but not quite all).
These libraries must be defined before you can use the methods within them. To do this in C#, we use a statement called using. Here are a couple of examples:
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
In order to keep track of what your code is doing, it is highly suggested that you put comments in your code. Comments are lines text in your code that don’t do anything when running your macro. To create a comment, preface the line of code with two forward-slashes //. Any text after these on this line will be considered a comment and in SharpDevelop will turn green.
Some other common C# functions you will need to know to get started with writing macros are if-else, foreach, and try-catch.
If-else is a function that C# uses to test if a statement is true or false. If an if-else statement is true the then statement is run. If it is false and you supply an else then the code in the else statement is run.
Example if and else:
if (level.Elevation == 0)
{
// do something if level is at 0 (true)
}
else
{
// do something if level isn’t at 0 (false)
}
The foreach function is used to iterate through a collection of elements. This function is very useful for applying a set of code to each element in the model you have collected (more on this later in the article on how to collect the elements).
Example foreach:
foreach (Element e in collection)
{
// do something to the element
// and then move on to the next one
}
The try function contains the guarded code that may cause an exception (error). The block of code is executed until an exception is thrown or it is completed successfully. This is useful for when trying to execute some code that might result in an error. You can catch the exception and display it in a dialog or skip the exception and move on to the next part of code.
Example try and catch:
try
{
// some code that might result in an error or not
}
catch
{
// do something with the error info if an error occurs
// show in a dialog to user if desired or move on
}
One of the most common things you will do while writing macros is to collect a set of elements that you want to do something to. Revit and C# together allow this in a few ways. The first way is to use what is called a filter. Filters allow you to filter the Revit model of elements down to a smaller number based on a rule set (filter) that you define. Thus filters are used to find a very specific set of elements that match the criteria in the filter.
Example filter code to collect all wall instances in the entire model:
// Find all Wall instances in the document by using category filter
ElementCategoryFilter filter = new ElementCategoryFilter(BuiltInCategory.OST_Walls);
// Apply the filter to the elements in the active document
// Use shortcut WhereElementIsNotElementType() to find wall instances only
FilteredElementCollector collector = new FilteredElementCollector(document);
IList<Element> walls =
collector.WherePasses(filter).WhereElementIsNotElementType().ToElements();
String prompt = "The walls in the current document are:\n";
foreach (Element e in walls)
{
prompt += e.Name + "\n";
}
TaskDialog.Show("Revit", prompt);
In the previous example we used an object called FilteredElementCollector. The FilteredElementCollector is a collection object that will hold all of the elements that meet the filter that we apply to it. This collection of elements can then be used in a foreach method to cycle through all elements one at a time. The FilteredElementCollector can be used to hold elements from the entire document (model), from a specified set of element ids, or from all the elements visible in a specified view.
Example FilteredElementCollector to collect all the elements visible in a 3D view:
FilteredElementCollector collector = new FilteredElementCollector(document);
// Use shortcut OfClass to get View elements
collector.OfClass(typeof(View3D));
// Get the Id of the first view
ElementId viewId = collector.FirstElementId();
// Test if the view is valid for element filtering
if (FilteredElementCollector.IsViewValidForElementIteration(document, viewId))
{
FilteredElementCollector viewCollector = new FilteredElementCollector
(document, viewId);
// Get all FamilyInstance items in the view
viewCollector.OfClass(typeof(FamilyInstance));
ICollection<ElementId> familyInstanceIds = viewCollector.ToElementIds();
// Do something to the families like reporting their id number in a dialog box
TaskDialog.Show(“Element Ids”, familyInstanceIds);
}
The Revit API also allows the use of Microsoft LINQ (Language Integrated Query) functions. LINQ allows you to use a database-like query system to filter the elements contained in a collection. It is much more powerful and easier to use, in my opinion, when working with collections of Revit elements.
Example LINQ query to get a collection of all the elements in the room category:
// get all elements in the model
FilteredElementCollector collector = new FilteredElementCollector(doc);
// filter out everything but rooms
ICollection<Element> collection = collector.OfClass(typeof(SpatialElement)).OfCategory(BuiltInCategory.OST_Rooms).ToElements();
Using collections to gather elements, using filters or LINQ to specify which elements are in the collection, and then iterating through the collection with foreach to perform a task on the elements is a core part of many macros that will assist you in managing your Revit models.
Example bringing together the concept of collections, LINQ, and foreach:
// get all elements in the model
FilteredElementCollector collector = new FilteredElementCollector(doc);
// filter out everything but rooms
ICollection<Element> collection = collector.OfClass(typeof(SpatialElement)).OfCategory(BuiltInCategory.OST_Rooms).ToElements();
// loop through each room in the model
foreach (Element e in collection)
{
// tell the element it is a room object
Room r = e as Room;
// report some room info to a dialog box
TaskDialog.Show("Rooms", "Name: " + r.Name + "\nNumber: " + r.Name
+ "\nBase Level: " + r.Level + "\nId: " + r.Id.ToString());
}
In order to access the Revit application and the Revit model database, your code needs to define objects that the Revit API will use to interface with these. These objects are called UIDocument and Document. The UIDocument contains access to the Revit application while the Document object contains access to the model elements. You will need to define these objects before using the Revit API in your macros.
Example defining UIDocument and Document objects:
// setup uidoc and doc for accessing the Revit UI (uidoc) and the Model (doc)
UIDocument uidoc = this.ActiveUIDocument;
Document doc = uidoc.Document;
There are lots of macros you can write that only query the model and don’t change any elements in the model. But if you do make changes to the model, you need to use what is called a Transaction. The Transaction method tells Revit that everything you do after starting the transaction should be applied when you complete (commit) the transaction. If you don’t put the changes into a transaction, the macro will give you an error and your changes will not take effect.
Example macro that duplicates a view as dependent five times with a transaction:
// Description: Create 5 Dependent Views for currently active view
public void CreateDependentViews()
{
// setup uidoc and doc for accessing the Revit UI (uidoc) and the Model (doc)
UIDocument uidoc = this.ActiveUIDocument;
Document doc = uidoc.Document;
// create a transaction for the current model (doc) and name it (this shows in the undo menu)
using(Transaction t = new Transaction(doc, "Duplicate View 5x"))
{
// start the transaction
t.Start();
// create a counter for incrementing alphabet (this is an integer)
int i = 0;
// loop through until you reach 5 (change 5 to increase/decrease the before running macro)
while (i < 5)
{
// duplicate the currently active view
ElementId dupViewId = uidoc.ActiveView.Duplicate(ViewDuplicateOption.AsDependent);
// get the new dependent view
View dupView = doc.GetElement(dupViewId) as View;
// use char command to get the letters A, B, C, D, E (changes each loop)
char c = (char) (i + 65);
// rename the dependent view to include the original name and the new Area
dupView.Name = uidoc.ActiveView.Name + " - AREA " + c.ToString();
// increment the char each loop (A, B, C, etc)
i++;
}
// finalize the transaction and commit the changes to the model
t.Commit();
}
}
The last example shows you a complete macro that will duplicate a view as dependent five times. After typing the macro code, you need to build the code. Building the code checks it for errors and if none are found it will make it available to run the macro inside the Macro Manager. Note that this doesn’t mean you won’t still have errors pop up; it checks just the syntax of your code, not the functionality. To build a module you can either hit the F8 key or go to the Build menu and choose Build Solution. You need to do this every time you edit your macro to get it to be the most current before you run the macro.
Figure 3: Run the Build Solution command before running your macro
Once the build is complete and you don’t have any errors, you can run the macro. In the case of the last example, you will end up with five new Dependent Views for the view that was current when you ran the macro.
Figure 4: Results of running CreateDependentViews macro on a floor plan
While this article is not an exhaustive A-Z of how to write macros in Revit, it should give you a good starting point to begin writing your macro toolbox. In addition to my examples above, you can search the internet as well as use the resources listed below.
Revit Coaster (my blog) : http://revitcoaster.blogspot.com/
The Building Coder : http://thebuildingcoder.typepad.com/
AEC DevBlog : http://adndevblog.typepad.com/aec/
Boost Your BIM : http://boostyourbim.wordpress.com/
SpiderInNet : http://spiderinnet.typepad.com/blog/
Autodesk Developer Network : http://usa.autodesk.com/adsk/servlet/index?siteID=123112&id=2484975
Autodesk Discussions : http://forums.autodesk.com/t5/Revit-API/bd-p/160
RevitForum.org : http://www.revitforum.org/third-party-add-ins-api-r-d/
AUGI Forums : http://forums.augi.com/forumdisplay.php?218-Revit-API