Friday, August 12, 2011

Using the Snoop Objects dialog from RevitLookup in RevitPythonShell

One of my main goals with RevitPythonShell is to provide a handy environment for exploring the Autodesk Revit API. But anyone seriously interested in exploring the Revit API should also be familiar with RevitLookup, formally known as RvtMgdDbg. This tool can be found in the SDK and includes a handy command for "snooping" a selected element called "Snoop Current Selection...". I use this so often, that I created a keyboard shortcut to invoke it. It provides vital information for working with Revit objects - information you probably want access to while inside an interactive RPS shell!

Unfortunately, you can't execute plugins while inside the RPS shell. Well, not through the Revit UI. You can access them via code. This blog is about a simple little module I wrote that let's you do this anywhere inside the shell:

>>>import revitsnoop

>>>snooper = revitsnoop.RevitSnoop(__revit__)

>>>snooper.snoop(doc.ProjectInformation)

And have a dialog like this popped up:

http://dl.dropbox.com/u/8112069/20110812-snoop-objects-dialog.png

Notice how you specify the object you want to snoop in code, as opposed to having to select it in the UI. Pretty nifty, eh?

How it works

The Autodesk.Revit.UI.UIApplication object has a property LoadedApplications that returns a list of all loaded external applications. As long as you have RevitLookup installed (quick test: can you find it in the Add-Ins tab?), it will show up in this list. It is pretty easy to find it:

rlapp = [app for app in uiApplication.LoadedApplications
         if app.GetType().Namespace == 'RevitLookup'
         and app.GetType().Name == 'App'][0]

I can't test with isinstance, since RPS doesn't know about RevitLookup yet and therefore I can't name the type. That is why I used GetType() and compared the namespace and name of the type.

Once we have the type, though, we can load the assembly and import RevitLookup as a module into IronPython:

import clr
clr.AddReference(rlapp.GetType().Assembly)
import RevitLookup

The statement import RevitLookup will only work after adding a reference to the assembly, but afterwards, we can start using the classes inside. To figure out what had to be done, I checked the source for RevitLookup, specifically the ExternalCommand that implements the "Snoop Current Selection..." functionality: RevitLookup.CmdSnoopModScope. It would have been possible to create an instance of this class and call its Execute method, but it turned out to be quicker and easier to just implement the body myself:

# See note in CollectorExt.cs in the RevitLookup source:
RevitLookup.Snoop.CollectorExts.CollectorExt.m_app = __revit__

# create a list of elements to snoop
elementSet = ElementSet()
elementSet.Insert(doc.ProjectInformation)

# create and show the dialog
form = RevitLookup.Snoop.Forms.Objects(elementSet)
form.ShowDialog()

This code is wrapped up in a module called revitsnoop for your convenience in the RevitPythonShell download section. To use it, place it inside one of the search paths configured in RPS.

The Code

For completeness sake, here is the whole module:

'''
revitsnoop.py

a simple library to access the RevitLookup snoop functionality
while inside the interactive shell...
'''
import clr
from Autodesk.Revit.DB import ElementSet

class RevitSnoop(object):
    def __init__(self, uiApplication):
        '''
        for RevitSnoop to function properly, it needs to be instantiated
        with a reverence to the Revit Application object.
        '''
        # find the RevitLookup plugin
        rlapp = [app for app in uiApplication.LoadedApplications
                 if app.GetType().Namespace == 'RevitLookup'
                 and app.GetType().Name == 'App'][0]
        # tell IronPython about the assembly of the RevitLookup plugin
        clr.AddReference(rlapp.GetType().Assembly)
        import RevitLookup
        self.RevitLookup = RevitLookup
        # See note in CollectorExt.cs in the RevitLookup source:
        self.RevitLookup.Snoop.CollectorExts.CollectorExt.m_app = uiApplication

    def snoop(self, element):
        elementSet = ElementSet()
        elementSet.Insert(element)
        form = self.RevitLookup.Snoop.Forms.Objects(elementSet)
        form.ShowDialog()

I think a logical further improvement would be to add some methods (e.g. snoopDocument and snoopApplication) for accessing the other useful functions of RevitLookup. But by now you should already know how to do it yourself!