Launching a Revit Command

One question that I frequently hear is how to launch a built-in Revit command programmatically.

We recently discussed a specific case related to this issue in some depth,

how to close the active document

and especially pointed out

why this is risky, undesirable and not supported
.

To close the active document, we used the SendKeys.SendWait method provided in the .NET framework System.Windows.Forms namespace.
Many of the other attempts that I have seen in the past to launch a Revit command were also based on this method, but unsuccessful.

Here is now a solution that actually does work, suggested by Robert Pleysier of

ALFA Development
in the Netherlands.
Instead of SendKeys, he uses the native Windows API PostMessage call to send the Windows messages WM_KEYDOWN and WM_KEYUP for each required keystroke to the Revit process main window handle.

The reason for Robert to launch a Revit command at all is the requirement to predefine which wall type should be active when the user enters the new wall creation command.
Since there is no API method available to programmatically predefine the current wall type to be used by this command, Robert selects an existing wall with the desired type and then uses the ‘Create Similar’ command to set up its type as the new default user interface wall type.
If no wall with the desired type exists yet in the model, a temporary wall is created and then deleted again.

Here is the question initially raised by Robert, and his own answer and solution:

Question: In the Revit API you can use the function PromptForFamilyInstancePlacement to place family symbols.

Is it possible to place a Wall with this function or another function from code?

I want to change the wall or other system family type by code in the property dialog.

After that I want to activate the Revit wall or stair function by code, so a user can draw his or her own wall or stair with the right wall or stair type.

Answer: I solved the issue by using a key press function “cs” for “create similar”.

Based on Robert’s example code showing how he did this, I implemented a new Building Code sample command CmdPressKey which illustrates his solution.

First of all, here is the helper class Press which accesses the Windows API methods and implements the public method Keys to actually send keystrokes using PostMessage. It requires the namespace System.Runtime.InteropServices:


public class Press
{
  [DllImport( "USER32.DLL" )]
  public static extern bool PostMessage(
    IntPtr hWnd, uint msg, uint wParam, uint lParam );
 
  [DllImport( "user32.dll" )]
  static extern uint MapVirtualKey(
    uint uCode, uint uMapType );
 
  enum WH_KEYBOARD_LPARAM : uint
  {
    KEYDOWN = 0x00000001,
    KEYUP = 0xC0000001
  }
 
  enum KEYBOARD_MSG : uint
  {
    WM_KEYDOWN = 0x100,
    WM_KEYUP = 0x101
  }
 
  enum MVK_MAP_TYPE : uint
  {
    VKEY_TO_SCANCODE = 0,
    SCANCODE_TO_VKEY = 1,
    VKEY_TO_CHAR = 2,
    SCANCODE_TO_LR_VKEY = 3
  }
 
  /// <summary>
  /// Post one single keystroke.
  /// </summary>
  static void OneKey( IntPtr handle, char letter )
  {
    uint scanCode = MapVirtualKey( letter,
      ( uint ) MVK_MAP_TYPE.VKEY_TO_SCANCODE );
 
    uint keyDownCode = ( uint )
      WH_KEYBOARD_LPARAM.KEYDOWN
      | ( scanCode << 16 );
 
    uint keyUpCode = ( uint )
      WH_KEYBOARD_LPARAM.KEYUP
      | ( scanCode << 16 );
 
    PostMessage( handle,
      ( uint ) KEYBOARD_MSG.WM_KEYDOWN,
      letter, keyDownCode );
 
    PostMessage( handle,
      ( uint ) KEYBOARD_MSG.WM_KEYUP,
      letter, keyUpCode );
  }
 
  /// <summary>
  /// Post a sequence of keystrokes.
  /// </summary>
  public static void Keys( string command )
  {
    IntPtr revitHandle = System.Diagnostics.Process
      .GetCurrentProcess().MainWindowHandle;
 
    foreach( char letter in command )
    {
      OneKey( revitHandle, letter );
    }
  }
}

With this in place, we can go ahead and implement the external command.
It requires two helper methods: GetFirstWallTypeNamed retrieves the appropriate wall type for a given wall type name, and GetFirstWallUsingType retrieves the first wall element encountered in the model making use of a given wall type.
Both of these obviously use filtered element collectors, and both of them even use parameter filters.
GetFirstWallTypeNamed uses the built-in parameter SYMBOL_NAME_PARAM and a string equality filter to do its job, i.e. to return the first wall type with the given name:


static WallType GetFirstWallTypeNamed(
  Document doc,
  string name )
{
  // built-in parameter storing this 
  // wall type's name:
 
  BuiltInParameter bip
    = BuiltInParameter.SYMBOL_NAME_PARAM;
 
  ParameterValueProvider provider
    = new ParameterValueProvider(
      new ElementId( bip ) );
 
  FilterStringRuleEvaluator evaluator
    = new FilterStringEquals();
 
  FilterRule rule = new FilterStringRule(
    provider, evaluator, name, false );
 
  ElementParameterFilter filter
    = new ElementParameterFilter( rule );
 
  FilteredElementCollector collector
    = new FilteredElementCollector( doc )
      .OfClass( typeof( WallType ) )
      .WherePasses( filter );
 
  return collector.FirstElement() as WallType;
}

GetFirstWallUsingType uses the built-in parameter ELEM_TYPE_PARAM and a numerical equality filter to retrieve all walls using the required wall type, because their corresponding parameter value will equal the wall type element id.
We don’t care which wall is used to launch the “Create Similar” command, so we simply return the first one encountered:


static Wall GetFirstWallUsingType(
  Document doc,
  WallType wallType )
{
  // built-in parameter storing this 
  // wall's wall type element id:
 
  BuiltInParameter bip
    = BuiltInParameter.ELEM_TYPE_PARAM;
 
  ParameterValueProvider provider
    = new ParameterValueProvider(
      new ElementId( bip ) );
 
  FilterNumericRuleEvaluator evaluator
    = new FilterNumericEquals();
 
  FilterRule rule = new FilterElementIdRule(
    provider, evaluator, wallType.Id );
 
  ElementParameterFilter filter
    = new ElementParameterFilter( rule );
 
  FilteredElementCollector collector
    = new FilteredElementCollector( doc )
      .OfClass( typeof( Wall ) )
      .WherePasses( filter );
 
  return collector.FirstElement() as Wall;
}

With the support class and helper methods in place, the external command mainline Execute method implementation is short and sweet:


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;
 
  // name of target wall type that we want to use:
 
  string wallTypeName = "Generic - 203";
 
  WallType wallType = GetFirstWallTypeNamed(
    doc, wallTypeName );
 
  Wall wall = GetFirstWallUsingType(
    doc, wallType );
 
  // select the wall in the UI
 
  uidoc.Selection.Elements.Add( wall );
 
  if( 0 == uidoc.Selection.Elements.Size )
  {
    // no wall with the correct wall type found
 
    FilteredElementCollector collector
      = new FilteredElementCollector( doc );
 
    Level ll = collector
      .OfClass( typeof( Level ) )
      .FirstElement() as Level;
 
    // place a new wall with the 
    // correct wall type in the project
 
    Line geomLine = app.Create.NewLineBound(
      XYZ.Zero, new XYZ( 2, 0, 0 ) );
 
    Transaction t = new Transaction(
      doc, "Create dummy wall" );
 
    t.Start();
 
    Wall nw = doc.Create.NewWall( geomLine,
      wallType, ll, 1, 0, false, false );
 
    t.Commit();
 
    // Select the new wall in the project
 
    uidoc.Selection.Elements.Add( nw );
 
    // Start command create similar. In the 
    // property menu, our wall type is set current
 
    Press.Keys( "CS" );
 
    // select the new wall in the project, 
    // so we can delete it
 
    uidoc.Selection.Elements.Add( nw );
 
    // erase the selected wall (remark: 
    // doc.delete(nw) may not be used, 
    // this command will undo)
 
    Press.Keys( "DE" );
 
    // start up wall command
 
    Press.Keys( "WA" );
  }
  else
  {
    // the correct wall is already selected:
 
    Press.Keys( "CS" ); // start "create similar"
  }
  return Result.Succeeded;
}

As you see, an arbitrary dummy wall of the required type is created if none previously exists.
For this, we start up a transaction of our own, so we are obviously using manual transaction mode.
And so we have to, since we have to close our transaction again before the Revit commands are invoked.

Robert says the following about this code:
Here is a part or our code to start a Revit command.
The aim of the code is to set a wall type current in the Revit property window.
We only start up the wall command with the API and let the user do the drawing of the wall.
This solution can also be used to launch other Revit commands.

When I start up the standard sample project rac_basic_sample_project.rvt, switch to Level 1, and launch this command from the Revit ribbon, I immediately enter the standard Revit wall command.
The desired wall type is active, in this case the type named “Generic – 203”, and I can immediately start placing new walls of that type.

There have been many other queries on how to programmatically set up the type before launching a Revit command, and this looks as if it could solve them.

I have to repeat the

warning about the risks involved with using this
, though,
and also point back to the

disclaimer
accompanying that warning.

Still, if this is just for your personal use, you might find it pretty handy.

Here is

version 2011.0.80.0
of The Building Coder samples including the complete source code and Visual Studio solution with the new command.


Comments

11 responses to “Launching a Revit Command”

  1. Dan Tartaglia Avatar
    Dan Tartaglia

    Hi Jeremy,
    The Document.Close() in the 2011 API did/does close my active document. The same code does not work in Revit 2012 :( With the above logic, how can I click the ‘application R’ button then select Close?
    Thanks,
    Dan

  2. Dear Dan,
    Oh, so you could use Document.Close to close the active document in Revit 2011. How strange. Others went to more effort to achieve this, by sending a Ctrl + F4 keystroke:
    http://thebuildingcoder.typepad.com/blog/2010/10/closing-the-active-document-and-why-not-to.html
    In Revit 2012, I believe you can close the active document as well or even better … but probably you will not need to, in your case, since the solution I just posted is simpler and more elegant:
    http://thebuildingcoder.typepad.com/blog/2011/05/cascaded-events-and-renaming-the-active-document.html
    Cheers, Jeremy.

  3. Hi,i wish to make a wall from multi selection , i mean i made refrence planes and dashed lines as primary skeleton for my project.
    is that anyway to make a wall from all refrence plane selected i am sure that coding an do it but i am just asking to see if you made that before or if it is possible to make this addin thanks

  4. Dear Ahmed,
    Yes, sure, this is perfectly possible. For example, please refer to the command Lab2_0_CreateLittleHouse in the Revit API labs XtraCs Labs2.cs module:
    http://thebuildingcoder.typepad.com/blog/2012/04/xtra-adn-revit-2013-api-training-labs.html
    It creates a little house as shown in several posts:
    http://thebuildingcoder.typepad.com/blog/2008/09/selecting-all-w.html
    http://thebuildingcoder.typepad.com/blog/2010/05/autojoinelements.html
    http://thebuildingcoder.typepad.com/blog/2010/06/set-tag-type.html
    The data for creating the walls and other BIM elements can come from anywhere.
    Cheers, Jeremy.

  5. Jeremy,
    I have a question. How can I use the key press out of this. Example, I want to use a button to zoom extents. The keystroke for that is ZE. I have a panel with buttons setup. I would ultimatly like to use this to open a specified drafting view, zoom extents, close hidden windows and then sync without modifying setting. this it possible?
    George

  6. Russ Green Avatar
    Russ Green

    Press.keys() doesn’t seem to work from my addin’s dialog. I seem to have to close the dialog and then use press.keys(). Is that correct?

  7. Dear Russ,
    If you are trying to send keys to Revit to launch a command, then of course this will not work as long as your modal form is still open, since this means that your own command is still active. Revit can only run one command at a time.
    Cheers, Jeremy.

  8. Michael Coffey Avatar
    Michael Coffey

    I was able to use Press.Keys() to update the Properties window from my modeless dialog. I first noticed that after selecting an element from my modeless dialog that I was able to hit the command to initiate the modeless dialog again and voila the properties updated to match the selected element. Since the form was already open, it recognized that and ignored the rest of the application. So by using Press.Keys() I send the shortcut command to initiate my modeless dialog after selecting the items in the UIDocument. This has worked great so far. It’s much better than the other workaround of opening a family. Thanks!

  9. Dear Michael,
    That sounds great! Thank you for letting us know. If you could package that as a generic stand-alone reproducible sample, I would love to test and publish it for others to enjoy as well. Thank you!
    Cheers, Jeremy.

  10. Most of the work I do is in 2014 still. I updated the ModelessForm_ExternalEvent sample for 2015 and realized the problem was corrected now making the workaround not needed for 2015. Seems as though the new UIDocument.Selection.SetElementIds() fixed it.

  11. Dear Michael,
    Glad to hear the problem is now resolved.
    Cheers, Jeremy.

Leave a Reply

Discover more from Autodesk Developer Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading