Using Module_Startup to run macro code when Revit starts

In previous posts, I have recommended staying away from this code that Revit creates in your macro file.  Now I will explain a situation when we need to get into it.

private void Module_Startup(object sender, EventArgs e)
{
}

The short summary is that code in Module_Startup runs automatically when Revit starts. It can be useful for subscribing to events, registering updaters for Dynamic Model Update, and for using the FailureDefinition.CreateFailureDefinition which is why it is relevant to this series of posts on PerformanceAdviser.

While working on a code sample to run a custom rule with the Performance Adviser, I ran the macro and Revit threw this exception:

RegistryLocked

The RevitAPI.chm help file tells us more about this restriction of FailureDefinition.CreateFailureDefinition:

The newly created FailureDefinition will be added to the FailureDefinitionRegistry. Because FailureDefinition could only be registered when Revit starting up, this function cannot be used after Revit has already started. Throws InvalidOperationException if invoked after Revit start-up is completed.

So we can’t start Revit normally and then, in the middle of our Revit session, run a macro that registers a FailureDefinition. Therefore we need a way to do this registration when Revit starts.

The Revit API Wiki provides the solution:

The Module_Startup method is called when a module loads and Module_Shutdown is called when a module unloads. For Application-level macro modules, Module Startup is called when Revit starts

So I move the code that calls CreateFailureDefinition from my RunRoomRule macro into Module_Startup. (RunRoomRule will be discussed in its own upcoming post). My Module_Startup now looks like this:

private void Module_Startup(object sender, EventArgs e)
{
    // Get the one instance of PerformanceAdviser in the Application
    PerformanceAdviser pa = PerformanceAdviser.GetPerformanceAdviser();

    // Create an instance of the RoomNotEnclosed rule class. Calling the RoomNotEnclosed() constructor is what calls CreateFailureDefinition.
     RoomNotEnclosed roomNotEnclosed = new RoomNotEnclosed();

     // Add this roomNotEnclosed rule to the PerformanceAdviser
    pa.AddRule( roomNotEnclosed.Id, roomNotEnclosed );
}

But when I compile my macro code I get the same exception shown in the screenshot above! Why? Because, in addition to Module_Startup running when Revit starts, it is also called when the macro project is rebuilt.

This creates a bit of a puzzle. I need to move the code to Module_Startup to only have it run when Revit starts. But to do this I need to compile the macro in the middle of my Revit session. But compiling the code in the middle of the Revit session fails because it calls Module_Startup.

The way out of this situation is to add try/catch handling of this exception. I have written about try/catch before and noted that, in general, for this blog I am not going to catch every exception that might occur. But here I have no choice. The exception that occurs during compilation needs to be caught so that compilation can succeed. On startup, the exception will not occur because at that time it will be legal to call CreateFailureDefinition.

The final code is:

private void Module_Startup(object sender, EventArgs e)
{
    try
    {
        // Get the one instance of PerformanceAdviser in the Application
        PerformanceAdviser pa = PerformanceAdviser.GetPerformanceAdviser();

        // Create an instance of the RoomNotEnclosed rule class
         RoomNotEnclosed roomNotEnclosed = new RoomNotEnclosed();

         // Add this roomNotEnclosed rule to the PerformanceAdviser
        pa.AddRule( roomNotEnclosed.Id, roomNotEnclosed );
    }    
    // Need to catch this exception because otherwise Revit will throw every time this is compiled because
    // Module_Startup and Module_Shutdown are called when the macro project is compiled.
    // And because the macro project will compile in the middle of the Revit session, calling RoomNotEnclosed()
    // will throw because it calls CreateFailureDefinition
    catch (Autodesk.Revit.Exceptions.ApplicationException)
    {}
}

(In my initial code I was catching the InvalidOperationException which is the specific sub-class of Autodesk.Revit.Exceptions.ApplicationException that is thrown by CreateFailureDefinition. But there is also another exception to deal with, an ArgumentException that occurs when the failure definition id has already been used to register an existing failure definition. ApplicationException is the parent class of both InvalidOperationException and ArgumentException, so catching ApplicationException takes care of both cases)

2 thoughts on “Using Module_Startup to run macro code when Revit starts

  1. Is it possible to put any kind of code in the startup routine for a project file? For example, when I open a Revit project, I would like it to load a folder of families (to get the latest version). Thanks.

Leave a comment