Showing posts with label revit. Show all posts
Showing posts with label revit. Show all posts

Thursday, January 15, 2015

Introducing the non-modal shell in RevitPythonShell (r223)

You have no idea how excited I am about this release! Revision r223 of the RevitPythonShell has been tested by a select few very brave people (Ehsan Iran Nejad and Callum Freeman) and seems to work. Normally I’m not so worried about pushing out new versions, but this one is… different.
The idea actually came from Ehsan. The Next Big Thing. Also known as the non-modal shell.
I know, I know - I need to get a lot better at coming up with good names for the various parts in RevitPythonShell, but for now, that is what it is called: Non-modal shell.
The non-modal shell is different from the standard RevitPythonShell shell in that it is… well… non-modal. What does that mean? Currently, when you start a shell in RPS, the shell gets the focus and you can’t interact with the rest of the Revit GUI until you close the shell again. This is cool for working with short scripts, trying stuff out etc, but we can do better:
The non-modal shell does not block the rest of Revit. You can select stuff in the view, make changes to the BIM etc. And then query the selection in the shell or do whatever you like.
With some exceptions.
You see, because the shell is non-modal, it is not running in the special thread Revit needs you to be in for accessing the API. You would get a lot of ugly errors, if there wasn’t some clever magic going on in the background that sends all input to the interactive shell to an implementation of IExternalEventHandler which Revit then runs in the correct thread and everything is fine and dandy.
What doesn’t work though, is using transactions in the interactive shell:
>>> t = Transaction(doc, 'test')
>>> t.Start()
Autodesk.Revit.DB.TransactionStatus.Started
>>> t.GetStatus()
Autodesk.Revit.DB.TransactionStatus.RolledBack
>>> t.Commit()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
Exception: The transaction has not been started yet (the current status is not 'Started'). A transaction must be started before it can be either committed or rolled back.
>>>
You can get around this by using the IronPython Pad - the little editor below the interactive shell. This allows you to run multiple lines at a time and those will be run in the same IExternalEventHandler so the problem does not show up.
You can find the installer for this (experimental) version of RevitPythonShell in the Downloads section of the project homepage.
I intend to post some more articles in the future that showcase some other new features in the r223 release, so you can expect some more goodies soon :)

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.