A Bit of Terminology

A reader was having difficulty compiling source code from a previous post, so I thought I’d post on some basics to explain a few terms and requirements about Revit macros:

A macro is an individual command that can be run in Revit. Here is the source and output from the simplest macro

simple

module is a collection of macros. To create a macro you must first create a module that will contain the macro. The macros in a module do not need to have any functional relationship. They can do completely different things and do not need to interact with each other in any way. You can put 1 macro in each module or 100 macros in each module. If you want to create utility functions (maybe to convert feet to meters or radians to degrees) then those functions need to be in the same module as the macros that use them. Here are a few restrictions:

  1. You cannot have two macros in the same module with the same name
  2. All macros in the same module must compile without errors for you to run any of the macros in that module
  3. Each module opens in its own session of SharpDevelop

module

Module_Startup and Module_Shutdown are special pieces of code that are automatically created by Revit when you create a new module. Their purpose is to give you a way to automatically run code when Revit starts and just before Revit exits.  Module_Startup code is also executed every time you compile the module. By default they are empty and have no code that they run, and in most cases this is fine.

Each module must have one and only one instance of Module_Startup and Module_Shutdown. If you delete them you will get errors like:

The name 'Module_Startup' does not exist in the current context

If you have them more than once (or any other macro more than once) you will get errors like:

Type 'BoostYourBIM1.ThisApplication' already defines a member called 'Module_Startup' with the same parameter types

When a sample on this blog requires Module_Startup code (like https://boostyourbim.wordpress.com/2013/02/14/set-adaptive-component-parameter-values-with-placement-point-coordinates/where it is used to register Dynamic Model Update triggers) I will include the startup code in the blog post. You can copy that startup code into your existing startup method, or replace your existing startup method with this new one. But if you paste the new Module_Startup code and leave your existing Module_Startup code that will cause the error that there is already a Module_Startup in that module.

Document.GetElement – Revit 2012 vs. 2013

I was asked how to modify this line in https://boostyourbim.wordpress.com/2012/12/06/sometimes-in-order-to-create-you-have-to-destroy/ so that it will run in Revit 2012.
GetElement
FamilyInstance famInst = doc.GetElement(id) as FamilyInstance;

In 2012, there was only one way to call the Document.GetElement method and that was with a reference.

In 2013, the input to GetElement can be a string (the element name), the element id, or the element reference. My code used the element id. This is fine for 2013 but not in 2012, where the input needs to be a reference.

Trying to compile the 2013 code in 2012 results in these errors.

Error	1	The best overloaded method match for 'Autodesk.Revit.DB.Document.GetElement(Autodesk.Revit.DB.Reference)' has some invalid arguments	
Error	2	Argument '1': cannot convert from 'Autodesk.Revit.DB.ElementId' to 'Autodesk.Revit.DB.Reference'

The solution here is to use the element id and the Document class’s Element property instead of the GetElement method.

FamilyInstance famInst = doc.get_Element(id) as FamilyInstance;

(Unlike most properties that can be called like Document.Element, this needs to be done as Document.get_Element)

Q) When is 1 divided by 3 = 0?

A) When you are doing integer division in C#.

Dividing two integers always produces an integer. That is why 1/3 = 0.  You can either cast the integers into doubles (in the number2 example) or use doubles instead of integers (number3 example).

double number = 1 / 3;
double number2 = (double)1 / (double)3;
double number3 = 1.0 / 3.0;
TaskDialog.Show("Division","Your results are:\n" + number + "\n" + number2 + " \n" + number3);

divisionl

 


			

Download links for the RevitAPI help files

After downloading these files, go to the File Properties dialog and click “Unblock” to allow them to display their contents.

Unblock

How to use Boost Your BIM source code to create working macros

A video showing the complete process of using use the code from these posts to create and compile a working macro.

https://boostyourbim.wordpress.com/2013/02/10/macro-to-quickly-fix-the-tofrom-room-parameter-for-doors/
https://boostyourbim.wordpress.com/2012/12/23/using-statements/

Stepping Into your macros and “Is this an import or a link?”

link

At Do U Revit, there is a post about finding and eliminating CAD imports from your Revit project. This is easy to do with a little bit of macro writing, and it also offers an opportunity for me to write about the “Step Into” macro command, which is a great way to explore the API and figure out how you might solve a specific problem.

In this case, I write a macro that prompts the user to select an element. If I select a DWG import and a DWG link, is there any way inside the API that I will be able to tell one from the other? If I want to write a filter to get all imports or all links, what data can I use to get them?

The answer is that imports and links both belong to the API’s ImportInstance class and this class has a property IsLinked (as shown in this screenshot from the API help file).

To learn how to find the answer, watch this video.

When testing for equality, compare names or ids, not the elements

I wrote this code to find which level types are used and not used.

public void levelTypes()
{
    Document doc = this.ActiveUIDocument.Document;
    string usedLevelTypes = "";
    string notUsedLevelTypes = "";
    foreach (LevelType levelType in new FilteredElementCollector(doc).OfClass(typeof(LevelType)).Cast<LevelType>())
    {
        if (new FilteredElementCollector(doc).OfClass(typeof(Level)).Cast<Level>().Where(level => level.LevelType == levelType).Count() > 0)
            usedLevelTypes += levelType.Name + ", ";
        else
            notUsedLevelTypes += levelType.Name + ", ";
    }
    TaskDialog.Show("Level Types",
                    "Used\n----\n" + usedLevelTypes + 
                    "\n\nNot Used\n--------\n" + notUsedLevelTypes);
}        

But it doesn’t work, because this always is false:

level.LevelType == levelType

Instead, do this

level.LevelType.Name == levelType.Name

Or do this

level.LevelType.Id == levelType.Id

You will be much happier and much less frustrated. I promise.

Resolving common compilation errors by adding Using statements

You write some macro code. You run the “Build Solution (F8)” command. But there are compilation errors! What to do?

One common cause is a lack of “using” statements. For example:

Code:
ElementId id = uidoc.Selection.PickObject(ObjectType.Element).ElementId;
Error:
'ObjectType' is inaccessible due to its protection level (CS0122)
Solution:
Add this line to the set of “using” statements at the top of your file
using Autodesk.Revit.UI.Selection;

Code:
IList<ElementId> ids = new List<ElementId>();
ICollection<ElementId> levels = new FilteredElementCollector(doc).OfClass(typeof(Level)).OfCategory(BuiltInCategory.OST_Levels).ToElementIds();

Errors:
The type or namespace name 'List' could not be found (are you missing a using directive or an assembly reference?) (CS0246)
The type or namespace name 'IList' could not be found (are you missing a using directive or an assembly reference?) (CS0246)
The type or namespace name 'ICollection' could not be found (are you missing a using directive or an assembly reference?) (CS0246)

Solution:
using System.Collections.Generic;

Code:
Reference r = HostObjectUtils.GetSideFaces(hostObj, ShellLayerType.Exterior).First();
Error:
'System.Collections.Generic.IList' does not contain a definition for 'First' and no extension method 'First' accepting a first argument of type 'System.Collections.Generic.IList' could be found (are you missing a using directive or an assembly reference?) (CS1061)
Solution:
using System.Linq;

How to Use These Macros – Basic Overview

UPDATE: I’ve posted a new video showing a simpler example of how to create a macro at https://boostyourbim.wordpress.com/2013/05/01/create-a-simple-macro-in-revit-2014/

If you are trying to get started with macros, here are some basics.

  1. In the Revit UI, go to the Manage tab
  2. In the Macros panel, click the Macro Manager button
  3. Macros can either be stored in the project (with the source code saved in the RVT file) or on your hard drive so that they are accessible for any RVT on your computer (these are called application macros). On this blog I am creating application macros. To do this, select the “Application” tab.
  4. Create Module
  5. Enter a name. Select C# or VB.NET (the code on my blog is C#). Push OK.
  6. Create Macro.
  7. Enter a name. Push OK.
  8. Go to the SharpDevelop application.
  9. Enter “using” statements at the top of the file – at least the two shown below in bold, possibly others depending on your code.
  10. Enter the text inside the { and } that define the body of the macro.
  11. Click F8. There should be no errors. If there are 2 warnings about processor architecture you can ignore them.
  12. Go back to the Revit Macros dialog. Select your new macro. Push Run.

Your code in SharpDevelop should look like this, depending on the specific macro you are implementing. In bold is the code I added (using statements & GetSheetParams macro).

/*
 * Revit Macro created by SharpDevelop
 * User: HP002
 * Date: 1/2/2013
 * Time: 4:54 PM
 * 
 * To change this template use Tools | Options | Coding | Edit Standard Headers.
 */
using System;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;

namespace MyModule
{
    [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
    [Autodesk.Revit.UI.Macros.AddInId("3528423F-D063-426C-A2ED-113794BF139E")]
    public partial class ThisApplication
    {
        private void Module_Startup(object sender, EventArgs e)
        {

        }

        private void Module_Shutdown(object sender, EventArgs e)
        {

        }

        #region Revit Macros generated code
        private void InternalStartup()
        {
            this.Startup += new System.EventHandler(Module_Startup);
            this.Shutdown += new System.EventHandler(Module_Shutdown);
        }
        #endregion

        public void GetSheetParams()
        {
            Document doc = this.ActiveUIDocument.Document;
            string sheetInfo = "";
            foreach (Element e in new FilteredElementCollector(doc).OfClass(typeof(ViewSheet)))
            {
                Parameter parameter = e.get_Parameter("CADFile");
                string cadFileData = parameter.AsString();
                sheetInfo += cadFileData + "\n";
            }
            TaskDialog.Show("CADFile", sheetInfo);
        }
    }
}

Commenting and Uncommenting in SharpDevelop

The SharpDevelop UI has a “Comment Region” command that you can use after selecting multiple lines of code.

In Microsoft Visual Studio, there are 2 different commands – Comment Region and Uncomment Region

In SharpDevelop there is no Uncomment Region command, but you can use Comment Region on code that is already commented and it will remove the comment notation.

CommentRegion

Using Statements

Here is the set of “using” statements that I currently have in my macros file

using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Collections.Generic;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Architecture;
using Autodesk.Revit.DB.Events;
using Autodesk.Revit.UI.Events;
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Selection;
using Autodesk.Revit.ApplicationServices;

For the output to Excel post I also used

using Microsoft.Office.Interop.Excel;

But as I noted in that post, that namespace and Autodesk.Revit.DB both have classes named Line and Parameter, so to avoid that I have commented out the Excel using statement and the macro that does the Excel output.

If you are missing “using” statements, the error you will see is like this one:

The type or namespace name ‘Room’ could not be found (are you missing a using directive or an assembly reference?) (CS0246) –

Getting Parameter Data

For “info347074” who asked at AUGI about getting the values of shared parameters that are shown in labels on a sheet, here is a little macro showing how it can be done. Getting the parameter values from the sheets is no different than getting the values from any other Revit element.

CADFile Parameter


public void GetSheetParams()
{
    Document doc = this.ActiveUIDocument.Document;
    string sheetInfo = "";
    foreach (Element e in new FilteredElementCollector(doc).OfClass(typeof(ViewSheet)))
    {
        // get the parameter
        Parameter parameter = e.get_Parameter("CADFile");

        // get the string value of the parameter
        string cadFileData = parameter.AsString();

        sheetInfo += cadFileData + "\n";
    }
    TaskDialog.Show("CADFile", sheetInfo);
}

Catching Exceptions

As Arnost mentioned in a comment to an earlier post, the methods Selection.PickObject and Select.PickObjects throw exceptions when the user cancels the pick operation. This most commonly happens when the user presses the ESC key. Speaking of the ESC, did you read this http://www.nytimes.com/2012/10/07/magazine/who-made-that-escape-key.html?

If you run this macro and push ESC instead of making a selection, Revit will throw the exception, your code will not catch the exception, and you will see this:

Reference r = uidoc.Selection.PickObject(ObjectType.Element);
TaskDialog.Show("Thanks!", "You selected " + doc.GetElement(r).Name);

exception

Much better is for your code to handle the error by using a try/catch block. For example, this code displays a TaskDialog when the exception is thrown and then caught.

try
{
    Reference r = uidoc.Selection.PickObject(ObjectType.Element);
    TaskDialog.Show("Thanks!", "You selected " + doc.GetElement(r).Name);
}
catch (Autodesk.Revit.Exceptions.OperationCanceledException exception)
{
    TaskDialog.Show("Oh No!", "You did not make a selection so PickObject threw an exception: " + exception.Message);
}

catch

Macros vs. Add-Ins – What’s the difference?

There are two different ways to work with the Revit API. In general, the code you write will be the same or almost the same in either case. Developing a macro is easier and simpler, but the trade-off is that you are limited in the development tools you use and how you share the macros with other people.  Developing Add-Ins involves more set-up and some technical mumbo-jumbo to register the add-ins with Revit. A main upside of Add-Ins is that you are better able to distribute them to other people.

Here’s a quick review:

Macros

SharpDevelop, a free and open-source development environment (known as an Integrated Development Environment, or IDE), is included in the Revit install. It is automatically connected to Revit so the process of creating macros is relatively simple. SharpDevelop is OK, but many people would rather use Microsoft Visual Studio. But to develop macros you must use SharpDevelop.

Macros are automatically registered with Revit when you compile your code in SharpDevelop. The macros are listed in the Macro Manager which is the UI that you must use to run them.

The code for your macro is stored in an RVT file if it is a document macro. If you create an application macro the code is stored independently on your computer. For the application macro examples I have been developing for this blog, Revit stores the source at C:\ProgramData\Autodesk\Revit\Macros\2013\Architecture\VstaMacros\AppHookup\BoostYourBIM\Source\BoostYourBIM.

You can share your macros with other people either by sending them the RVT file (if it is a document macro) or sending them the source code (but that will take a bit of effort on their part to create a macro on their install of Revit to generate the same macro.

Add-Ins

Add-Ins are developed externally to Revit, usually using Microsoft Visual Studio, the free version called Visual Studio Express, or some other IDE.

The code for your Add-In gets compiled into a DLL file. An ADDIN file needs to be created so that Revit can discover the DLL. The ADDIN file goes into a place like C:\Users\HP002\AppData\Roaming\Autodesk\revit\Addins\2013. The ADDIN and DLL can be distributed to other users to let them run the command.

Constructors and creating a new transaction

A reader asked for some more information about this line of code:

Transaction t = new Transaction(document,”Modify Mark values”);

What is happening here is that a new Transaction object is being created using the Transaction constructor. The transaction must be associated with a document (you can have several open documents co-existing simultaneously in your Revit API code).

You will also need to give your constructor a name. This can be done when the transaction is created as above, or you can use the constructor that requires only a document and specify the name later using the Transaction.SetName() method. The name of the transaction can be any string that you want. If the transaction is committed (which means that it successfully completed) then the end-user will see the name of the transaction in the Undo menu.

Creating a transaction does not automatically start the transaction. That must be done separately with the Start method. When you are finished with the changes to the model, use the Commit method. Or you can use the Rollback method if you don’t want to commit the changes that were made after starting the transaction.

 

Revit API Wiki and RevitAPI.chm

For general reference and all sorts of great info about the Revit API, I recommend:

  1. The Revit API Developer’s Guide Wiki at http://wikihelp.autodesk.com/index.php?title=Revit/enu/2013/Help/00006-API_Developer%27s_Guide.
  2. The RevitAPI.chm help file. This is part of the Revit SDK (Software Developers Kit) which is included in the Revit install in the ‘Install Tools and Utilities’ option on the install main page. Or you can download it at https://boostyourbim.wordpress.com/2013/02/16/download-links-for-the-revitapi-help-files/

Use a Using block with Transactions

The code I originally had below in the Duplicate Mark sample works fine when everything goes according to plan. But a more robust approach would use a “using” block as shown below.

using( Transaction transaction = new Transaction(document,"action") )
{
   transaction.Start();
  // ....
   transaction.Commit();
}

This goes for every transaction as well as every transaction group and sub-transaction. The objective is to make sure those objects are properly closed (particularly in case of an exception). More on the subject is in the MSDN C# reference.