Tuesday, March 23, 2010

Adding canned scripts to the Revit RibbonPanel

I feal really smug about myself today! I finally figured out how to add
shortcuts to the canned python scripts defined in RevitPythonShell.xml to the
Revit RibbonPanel. I'd like to share some of the fancy magic that went into r65.

RevitPythonShell lets you define a list of named scripts in the
RevitPythonShell.xml file. An exerpt from my personal file looks like this:

  <Commands>
    <!-- a list of preconfigured commands -->
    <Command name="dumpdpvfolder" src="C:\RevitPythonShell\Commands\dpvdumpfolder.py"/>
    <Command name="read model" src="C:\RevitPythonShell\Commands\readmodel.py"/>
    <Command name="Report" src="C:\RevitPythonShell\Commands\report.py"/>
    <Command name="View Model" src="C:\RevitPythonShell\Commands\viewmodel.py"/>
  </Commands>

These are commands I use all the time during development of the
DesignPerformanceViewer (DPV). I use these commands to test parts of the DPV, run
automated tests on lots of input projects etc.

So far, these commands where only displayed in a toolbar above the interactive
shell. On the RibbonPanel provided by Autodesk Revit Architecture 2010 only a
single button was displayed: Open Python Shell. The main reason being: To add
an item to the RibbonPanel, you have to provide a path to an assembly and the
name of a type inside that assembly implementing IExternalCommand.

In the case of RevitPythonShell, we can't specify such an assembly. There is none.
And we can't use a generic type as the IExternalCommand and load the python script
in the Execute() method either, since the arguments to Execute don't contain any hints as
to which button was clicked!

This is where dynamic assemblies and Reflecion.Emit enters the stage: When RevitPythonShell
starts up (IExternalCommand.OnStartup), the list of canned commands is read and a dynamic assembly
is created to hold a dynamically created class for each canned command defined in RevitPythonShell.xml.

This is done with Reflection.Emit in the method RevitPythonShellApplication.CreateCommandLoaderAssembly():

        /// <summary>
        /// Creates a dynamic assembly that contains types for starting the canned commands.
        /// </summary>
        private static void CreateCommandLoaderAssembly()
        {
            var assemblyName = new AssemblyName {Name = "CommandLoaderAssembly", Version = new Version(1, 0, 0, 0)};
            var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);
            var moduleBuilder = assemblyBuilder.DefineDynamicModule("CommandLoaderModule", "CommandLoaderAssembly.dll");

            foreach (var command in GetCommands())
            {
                var typebuilder = moduleBuilder.DefineType("Command" + command.Index,
                                                        TypeAttributes.Class | TypeAttributes.Public,
                                                        typeof(CommandLoaderBase));

                var ci = typeof(CommandLoaderBase).GetConstructor(new[] { typeof(string) });

                var constructorBuilder = typebuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[0]);
                var gen = constructorBuilder.GetILGenerator();
                gen.Emit(OpCodes.Ldarg_0);                // Load "this" onto eval stack
                gen.Emit(OpCodes.Ldstr, command.Source);  // Load the path to the command as a string onto stack
                gen.Emit(OpCodes.Call, ci);               // call base constructor (consumes "this" and the string)
                gen.Emit(OpCodes.Nop);                    // Fill some space - this is how it is generated for equivalent C# code
                gen.Emit(OpCodes.Nop);
                gen.Emit(OpCodes.Nop);
                gen.Emit(OpCodes.Ret);                    // return from constructor
                typebuilder.CreateType();
            }
            assemblyBuilder.Save("CommandLoaderAssembly.dll");            
        }

Now, this is the first time I have ever emitted IL in my life. Naturally, I wanted to get as much done in C# as possible, so the types
generated all inherit CommandLoaderBase:

    /// <summary>
    /// Starts up a ScriptOutput window for a given canned command.
    /// 
    /// It is expected that this will be inherited by dynamic types that have the field
    /// _scriptSource set to point to a python file that will be executed in the constructor.
    /// </summary>
    public abstract class CommandLoaderBase: IExternalCommand
    {
        protected string _scriptSource = "";

        public CommandLoaderBase(string scriptSource)
        {
            _scriptSource = scriptSource;
        }

        public IExternalCommand.Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {
            var executor = new ScriptExecutor(commandData, message, elements);

            string source;
            using (var reader = File.OpenText(_scriptSource))
            {
                source = reader.ReadToEnd();
            }

            var result = executor.ExecuteScript(source);
            message = executor.Message;
            switch (result)
            {
                case (int)IExternalCommand.Result.Succeeded:
                    return IExternalCommand.Result.Succeeded;
                case (int)IExternalCommand.Result.Cancelled:
                    return IExternalCommand.Result.Cancelled;
                case (int)IExternalCommand.Result.Failed:
                    return IExternalCommand.Result.Failed;
                default:
                    return IExternalCommand.Result.Succeeded;
            }
        }
    }

This base class has a constructor that accepts the path to a python script and
creates a ScriptExecutor (check RevitPythonShell source code) to execute that script in its implementation of
IExternalCommand.Execute().

And now the Reflection/IL stuff starts to make sense: Create a type inheriting from CommandLoaderBase, adding a parameterless constructor (called by
Revit) which in turn calls the base constructor with the path to the script. The resulting IL looks something like this:

    .class public auto ansi Command0
        extends [RevitPythonShell]RevitPythonShell.CommandLoaderBase
    {
        .method public specialname rtspecialname instance void .ctor() cil managed
        {
            .maxstack 2
            L_0000: ldarg.0 
            L_0001: ldstr "C:\\RevitPythonShell\\Commands\\dpvdumpfolder.py"
            L_0006: call instance void [RevitPythonShell]RevitPythonShell.CommandLoaderBase::.ctor(string)
            L_000b: nop 
            L_000c: nop 
            L_000d: nop 
            L_000e: ret 
        }

    }

With "Command0" being the name of the type to instantiate for the first canned script in RevitPythonShell.xml. The C# equivalent looks like this:

    public class Command0 : CommandLoaderBase
    {
        // Methods
        public Command0() : base(@"C:\RevitPythonShell\Commands\dpvdumpfolder.py")
        {
        }
    }

I used Red Gate's .NET Reflector to disassemble the resulting assembly and also as a hint on how to write the Reflection.Emit stuff (created a dummy subclass of CommandLoaderBase with the desired constructor, compiled and then disassembled in Reflector).

Now I need to add a nice interface to manage the canned commands - any ideas?

Wednesday, March 10, 2010

Using RevitPythonShell to dynamically load plugins for debugging

Ever since I read Jeremy Tammiks blog post Reload an Add-In to Debug, where he
describes about John Morse's technique for dynamically loading plugins without
having to restart Revit, I have been itching to get this working in
RevitPythonShell!

Finally, with revision 55 and the loadplugin script, I can rapidly iterate
working on my C# projects - no more waiting for Autodesk Revit to restart after
fixing a small mistake!

Here is a little sampe session from the project page:

>>>import loadplugin
>>># use actual path here... (if you don't already have RvtMgdDbg.dll installed, you probably want to go and do so 
>>># right now - check ADN!)
>>>assembly = loadplugin.loadAssembly(r'C:\...\Visual Studio 2008\Projects\RvtMgdDbg\bin\RvtMgdDbg.dll')
>>># result is an assembly object
>>>assembly

<Assembly RvtMgdDbg, Version=1.0.3671.26458, Culture=neutral, PublicKeyToken=null>

>>># pretty cool: IronPython lets you use the assembly like a namespace
>>># also note the use of the special predefined variables for executing plugins!
>>>assembly.RvtMgdDbg.CmdSnoopDb().Execute(__commandData__, __message__, __elements__)

(<Autodesk.Revit.IExternalCommand+Result object at 0x000000000000002D [Succeeded]>, '')
>>>


So how dows it work? Basically, you load an assembly as a byte array and then
use Assembly.Load to load the assembly from the byte array. Dynamically loading
assemblies this way does not lock them on the hard disk, allowing Visual Studio
to overwrite the DLLs at build time.

I suggest you write a test script for your plugin, that you can then save as a
canned command in RevitPythonShell, that loads it via loadplugin.loadAssembly and
does some exercising: Instantiate some types, execute IExternalCommand instances etc.

To make interaction with plugins easier, RevitPythonShell now supports some predefined variables, that give you more access to the context in which the plugin itself was loaded.
This lets you simulate the user clicking on PushButtons in the Ribbon.

Also, I have included a fork of the IronPythonTextBox control, to make plain interactive
use of RevitPythonShell smoother.

Please let me know, if this is of any use to you. Also, I would really appreciate some help, especially in getting the system more user friendly (e.g. script repository etc.).