Wednesday, December 23, 2009

Adding your plugin to the [ExternalApplications] section of Revit.ini

For the installer for RevitPythonShell, I need to programmatically add the plugin to the [ExternalApplications] section of Revit.ini. Instead of writing a one-off solution, I decided to create a little commandline tool that can be reused for my next project or even your project: addrevitplugin.exe. You can look at the source too!
The tool is written using AutoIt v3, a BASIC derivative with lots of useful functions for automating windows tasks. Expect to see some more samples of such scripts on this blog...
The commandline options to addrevitplugin.exe are:
  • EAClassName: This corresponds to the key "EAClassName#" in the Revit.ini file
  • EAAssembly: (optional) This corresponds to the key "EAAssembly#" in the Revit.ini file and is the full path to the .NET assembly containing the plugin class.
Using addrevitplugin.exe, you can either add, update or delete a plugin entry in the "ExternalApplications" section. By specifying EAClassName without EAAssembly, the corresponding plugin is deleted from Revit.ini. If EAAssembly is provided, then the plugin is either added (if it was not yet installed) or updated with the assembly.

I also use this tool in conjunction with Launchy to quickly switch between environments when developing Revit plugins: Create a batch file for each location of your plugin (Debug, Release, Installed) with contents similar to:

addrevitplugin.exe RevitPythonShell.RevitPythonShellApplication "C:\Projects\revitpythonshell\RevitPythonShell\bin\Debug\RevitPythonShell.dll"

Make sure the second argument is enclosed in double quotes if it contains spaces. Create a separate file for each environment.

I named the files debug_RevitPythonShell.bat, release_RevitPythonShell.bat and installed_RevitPythonShell.bat. By saving these files in a directory scanned by Launchy, I can quickly switch between the environments before starting Autodesk Revit Architecture 2010.

Note:If you are not using Autodesk Revit Architecture 2010 on a 32bit system, you will want to change the GUID used to look up the install path in the script addrevitplugin.au3. You will then need to recompile the script using Aut2Exe.exe, which can be obtained from the Auto It website.

Update: I have renamed addrevitplugin.exe to rac32plugin.exe and rac64plugin.exe so that now a 32-bit and a 64-bit version comes precompiled. Also, the scripts have changed somewhat. Please check the project website.

Friday, December 18, 2009

Introducing RevitPythonShell

RevitPythonShell is a little tool I built to make life in RevitAPI-land a little easier.

When writing plugins for Autodesk Revit Architecture 2010, you have to restart Revit each time you create a new build and manually click your way to the plugins functionality. If you are not so sure about how a given method or property from the API works (or what values to expect when reading it), you will end up with a lot of these edit-compile-run cycles. I don't know how fast your machine is, but starting Revit on mine is not as snappy as I would like. Plus, you can't really experiment, can you?

So, with RevitPythonShell, you can. It embeds IronPython, a .NET port of the python language as a plugin. The main window provides a simple text editor that lets you write a script and execute it. The most useful script is probably this one:

import code
code.interact(None, None, 
        {
            '__name__': '__console__', 
            '__doc__':None, 
            '__revit__': __revit__
        })


This will open up an interactive interpreter loop (known as a REPL), that will let you explore the RevitAPI. And by explore, I mean type a statement, hit enter, see the results, carry on. It doesn't get easier as this!

The above script is actually saved as a "canned command" - an button (in this case named "Interactive") in the toolbar above the main window. Canned commands like these are an ideal place to save scripts you have developed and found useful - a sort of mini-plugin-in-a-plugin architecture.

The default script shown when you start the RevitPythonShell plugin is this one:


# type in a python script to run here...
try:
  # assumes the following file exists: C:\RevitPythonShell\current.py
  import current  
  reload(current)
  
  current.main(__revit__)
except:
  import traceback
  traceback.print_exc()

It imports the module current.py from the search path (normally C:\RevitPythonShell) which contains a script you are currently working on. Clicking the button Execute (or pressing CTRL+RETURN) will execute the contents of the main window, which will in turn import the module and run its main() method.

Loading a script from the file system instead of typing it into the simple editor provided by RevitPythonShell is probably a good idea, since most editors make life a lot easier compared to the TextBox control used here - even Notepad.exe would be preferable!

Note the parameter __revit__ passed into the main method: This is a reference to the Autodesk.Revit.Application API object used by plugins to gain access to Revit.

I will be showing off some of the things you can do with RevitPythonShell in later posts.

Wednesday, December 9, 2009

Using C# extension methods for Revit Element Parameters

One of the places using C# extension methods shines is writing getters and setters for element parameters in Autodesk Revit Architecture 2010. Parameters are user defined meta data bound to either an instance or a type in Revit. Accessing these is a bit cumbersome:

public IExternalCommand.Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
    var document = commandData.Application.ActiveDocument;    
    
    Parameter parameter = document.ProjectInformation.get_Parameter("BuildingCategory");
    if (parameter != null)
    {                
        var buildingCategory = parameter.AsInteger();
        // do something with this information
    }        
}

Imagine typing all that code every time you want to access your parameter in code! Of course you will wrap it up in a method, but wrapping it in an extension method allows for neat syntax:

public static int GetBuildingCategory(this ProjectInfo self)
{
        
    Parameter parameter = self.get_Parameter("BuildingCategory");
    if (parameter != null)
    {
                
        return parameter.AsInteger();
    }        
    // you could also throw an exception here...
    return 0;
}


Now, you can access the property through the element itself, and it shows up in intellisense!

int buildingCategory = document.ProjectInformation.GetBuildingCategory();

In fact, I go a step further in my project, utilizing the t4 templating engine to generate the parameter accessors as well as routines to bind them to the document from an XML file. But that will have to wait for another blog post.

Thursday, December 3, 2009

Using C# extension methods to extend plugin architectures

I've spent a lot of time recently writing a plugin for Autodesk Revit Architecture 2010. Extending Revit involves writing managed code that implements a special interface (Autodesk.Revit.IExternalApplication) that gets called by the host application when special events occur - the application starts up, one of your toolbar buttons was clicked, a document was opened / closed / saved etc.
Access to the host is provided as a parameter to the methods defined in the interface. One of the main objects of interest for the work I'm doing is the Autodesk.Revit.Document class. This is my window into Revit documents, providing information about elements in the document.
The catch: You cannot change how these objects behave, since they are provided by the host. If Autodesk.Revit.Document is missing a Rooms property that lists all the rooms in the document, well, you are out of luck.
The traditional object oriented solution to this problem is inheritance. You subclass the lacking class in question and add the desired functionality. This will not work in a plugin situation. You have no control of how Revit instantiates its document object.
You could decorate the object with new functionality: Implement the Document interface (in this case you would have to extract the interface first) and delegate all invocations to a stashed away instance received via Revit. This can fly, but its pretty ugly to do by hand - you might want to generate this.
C# 3.0 comes with a new solution to this problem: Extension Methods. Lets look at some code.
/// <summary>
/// Provides some utility methods for working with Revit.Document objects.
/// </summary>
public static class RevitDocumentExtensions
{
    /// <summary>
    /// Returns a list of all room objects in a Revit document.
    /// This excludes room
    /// </summary>
    public static IEnumerable<Room> GetRooms(this Document document)
    {
        var roomElements = new List<Element>();
        var filterFactory = document.Application.Create.Filter;
        var filter = filterFactory.NewTypeFilter(typeof(Room));
            document.get_Elements(filter, roomElements);

        // filter the rooms so only the placed ones are returned.
        return roomElements.Cast<Room>().Where(r => r.Location != null);
    }
}
Given an Autodesk.Revit.Document object, GetRooms can be called like so:
var rooms = RevitDocumentExtensions.GetRooms(document);
This is nothing new, hardly exciting, but works.
Did you notice the magic keyword this in the functions signature?
public static IEnumerable<Room> GetRooms(this Document document)
That's new. We are still talking static methods here, and that is all the .NET runtime ever sees, but the C# compiler will allow you to use this method syntactically as if it were defined in the Document class! So our usage chages to this:
var rooms = document.GetRooms();
Neat, huh? It looks as if we have subclassed Autodesk.Revit.Document and added a new instance method. Using this technique, a plugin API can be extended to better suit the needs of the plugin.
I will post more examples on how I used extension methods to augment Revit in my next post.