Melbourne Day One

These days right now represent a unique time in our life history. Venus and Jupiter are in a stunning conjunction, closer together right now than they will ever be again for the next two hundred years. If you have a chance, don't forget to look up at the sky in the evenings!

Venus Jupiter conjunction

In another unique and far-away event, at least from my normal habitat, we completed the first day of the Revit API Training here in Melbourne.

Talking about being far away from my everyday habitat, I went for a walk with my hosts Kim, Rob, Erika and Lewis and their friends Geoff, Vivienne and Alice up the Anakie Gorge in the Brisbane Ranges National Park last Saturday, enjoying the wonderful Australian flora:

Anakie Gorge

Back in the city and the Autodesk training room in Queen's Road we are a nice mix of participants, many with significant Revit product and some Revit API experience, others with zero of both, but decades of professional application development behind them and a wish to make use of it within the Revit API.

On the first day we went through the basics of the Revit API, all of which are also covered by the materials provided by the Revit Developer Center and described in more detail in the hands-on training preparation suggestions.

Command Instantiation and Element Picking

One little sample command that we ended up developing together simply demonstrates interactive element selection and changing the name of an element type. In the case of a wall, the attempt to change the name of the wall itself throws an exception, because the wall instance is actually just reflecting its type name. For other instances it throws no exception but has no effect either. Changing the code to modify the name of the element type instead of the instance works in both these cases.

While playing around with this, one interesting and previously unanswered question that we stumbled across was on the instantiation of the external command implementation class.

In AutoCAD.NET, a separate instance of a command implementation class is created for each document, and then reused for future command invocations in that document. How is this handled in Revit?

The code required to explore and answer this question is very simple: just implement a public constructor for the class which counts and prints out the number of invocations.

  [Transaction( TransactionMode.Manual )]
 
  public class Command : IExternalCommand
  {
    static int _instance_count = 0;
 
    public Command()
    {
      Debug.Print( "{0} instances.", 
        ++_instance_count );
    }
 
    // . . .
  }

As it turns out, a new instance of the command class is created every time the command is launched.

Oops. Looking in a bit more depth, we discovered that this is in fact clearly stated in the developer guide, which says: "When a command is selected, a command object is created and its Execute() method is called. Once this method returns back to Revit, the command object is destroyed."

This confirms what every careful programmer would do subconsciously anyway: keep your command class implementation as light as possible, since it will be re-instantiated for each call of your command.

If you need to store any large amounts of data, then do so either in static variables, or, more cleanly, in separate singleton classes elsewhere.

The code we ended up with to select an element and rename its type looks like this:

public Result Execute(
  ExternalCommandData commandData,
  ref string message,
  ElementSet elements )
{
  UIApplication uiapp = commandData.Application;
  UIDocument uidoc = uiapp.ActiveUIDocument;
  Application app = uiapp.Application;
  Document doc = uidoc.Document;
 
  Element e;
 
  try
  {
    Reference r = uidoc.Selection.PickObject( 
      ObjectType.Element, 
      "Please pick an element." );
 
    e = doc.get_Element( r.ElementId );
  }
  catch( RvtOperationCanceledException )
  {
    return Result.Cancelled;
  }
 
  using( Transaction tx = new Transaction( doc ) )
  {
    tx.Start( "Rename Element" );
 
    ElementId id = e.GetTypeId();
 
    Element type = doc.get_Element( id );
 
    if( null != type )
    {
      type.Name = "Melbourne " + type.Name;
    }
 
    tx.Commit();
  }
  return Result.Succeeded;
}

We used this to discuss a number of basic aspects of add-in creation:

Filtered Element Collector and Using Parameter Filter for Non-Empty String

In a second step, we had a look at a filtered element collector to access the Revit database contents.

We decided that parameter filters are of special interest, and explored how to filter for an empty and a non-empty string value.

Here is the code that we ended up with:

public Result Execute(
  ExternalCommandData commandData,
  ref string message,
  ElementSet elements )
{
  UIApplication uiapp = commandData.Application;
  UIDocument uidoc = uiapp.ActiveUIDocument;
  Application app = uiapp.Application;
  Document doc = uidoc.Document;
 
  BuiltInParameter bip 
    = BuiltInParameter.ALL_MODEL_MARK;
 
  ParameterValueProvider provider
    = new ParameterValueProvider(
      new ElementId( bip ) );
 
  // Filter for an empty string:
 
  //FilterStringRuleEvaluator evaluator 
  //  = new FilterStringEquals();
 
  // Filter for an non-empty string:
 
  FilterStringRuleEvaluator evaluator 
    = new FilterStringGreater();
 
  FilterStringRule rule = new FilterStringRule( 
    provider, evaluator, "", false );
 
  bool inverted = false;
 
  ElementParameterFilter filter
    = new ElementParameterFilter( rule, inverted );
 
  FilteredElementCollector col
    = new FilteredElementCollector( doc )
      .WhereElementIsNotElementType()
      .WherePasses( filter );
 
  foreach( Element e in col )
  {
    Parameter p = e.get_Parameter( bip );
 
    Debug.Print( "'{0}': '{1}'", 
      e.Name, 
      (null==p? "null" : p.AsString() ) );
  }
 
  return Result.Succeeded;
}

In its current, final state, it uses a parameter string filter to retrieve and list all elements with a non-empty Mark parameter value.

To do so, we search for any string values greater than the empty string "".

We also tried using a null value instead of the empty string, but that throws a rather inelegant exception in the FilterStringRule constructor saying

We also tested searching for all empty string values using a FilterStringEquals evaluator, and that worked fine as well.

Evernote and a Revit Product and Family Tutorial

During our explorations, we underlined the importance of families and the family API. I mentioned the Autodesk Revit 2010 Families Guide several times in the past. It is free and covers the basics well together with Google, especially some important Revit MEP content best practices.

For more background, especially on Revit MEP, the Learning Autodesk Revit MEP 2012 video training by Simon Whitbread, Don Bokmiller and Joel Londenberg is recommended.

One neat little non-Revit-API topic that popped up was the handy and free little Evernote utility for storing and sharing notes across the cloud and various mobile devices.