The Performance Adviser API

Unbelievable as it may seem, there are still a number of new areas of functionality added in the Revit 2012 API that we have not even got around to covering here yet at all.
One of these is the performance adviser.
Here is an article on that by my colleague Saikat Bhattacharya from the ADN AEC newsletter.

The Performance Adviser API added in Revit 2012 allows developers to execute a set of built-in or custom rules against a given model, and to display a warning dialog about the elements that fail to satisfy the rules. You can use this feature to flag the user about possible performance degradations, and to create your own rules that could be run on a model to check for adherence to certain design guidelines. For example, you may want to signal the user where walls may be overlapping.

This article provides an overview and discusses how to
access performance adviser rules,
execute rules, and
define custom rules.

Accessing Performance Adviser Rules

The performance adviser rules are a set of application-wide rules. To work with performance adviser rules, we use an application-wide, singleton class called PerformanceAdviser. This class is a registry of all the performance checking rules as well as an engine to execute the rules. You can access the singleton PerformanceAdviser instance using a PerformanceAdviser.GetPerformanceAdviser static method.

Once we have access to the PerformanceAdviser object, we can use the GetAllRuleIds method to obtain a list of rule IDs registered in the application. We can then iterate through each rule ID and get the rule information for the corresponding rule ID, using the methods, such as GetRuleName and GetRuleDescription.

The following command illustrates how to list all the registered performance adviser:


/// <summary>
/// List all registered performance adviser rules
/// </summary>
[Transaction( TransactionMode.Manual )]
public class ListPerformanceAdviserRules
  : IExternalCommand
{
  public Autodesk.Revit.UI.Result Execute(
    ExternalCommandData commandData,
    ref string message,
    ElementSet elements )
  {
    // Get access to Performance Adviser object
 
    PerformanceAdviser adviser
      = PerformanceAdviser.GetPerformanceAdviser();
 
    // Access the rules
 
    IList<PerformanceAdviserRuleId> ruleIds
      = adviser.GetAllRuleIds();
 
    String ruleInfo = String.Empty;
 
    // Loop through each rule Id and get rule name and description
 
    foreach( PerformanceAdviserRuleId ruleId
      in ruleIds )
    {
      ruleInfo += "n" + adviser.GetRuleName( ruleId )
        + " : " + adviser.GetRuleDescription( ruleId );
    }
 
    TaskDialog.Show( "Existing Rules Info", ruleInfo );
 
    return Result.Succeeded;
  }
}

Executing a Performance Adviser Rule

Once you know the ids of rules that you are interested in, you can use the PerformanceAdviser.ExecuteRules method to execute the given rules.
This method takes a document and a set of rule ids as input arguments.
It returns a list of FailureMessages, which we can iterate through and display, for instance, in a standard Revit warning dialog using Document.PostFailure method.

For an introductory discussion about Failure Posting and Handling, please refer to

this introduction
,

its follow-up
,
and the ADN AEC Customization Newsletter – BIM Edition – Winter 2011.

The following command shows a simple example of executing the overlapping walls performance adviser rule:


/// <summary>
/// Execute overlapping walls? 
/// performance adviser rule.
/// </summary>
[Transaction( TransactionMode.Manual )]
public class ExecutePerformanceRule : IExternalCommand
{
  public Result Execute(
    ExternalCommandData commandData,
    ref string message,
    ElementSet elements )
  {
    Document doc = commandData.Application
      .ActiveUIDocument.Document;
 
    // Access the Performance Adviser object
 
    PerformanceAdviser adviser
      = PerformanceAdviser.GetPerformanceAdviser();
 
    // List all the rules
 
    IList<PerformanceAdviserRuleId> ruleIds = adviser.GetAllRuleIds();
 
    // Create an empty list for 
    // the rules to be executed
 
    IList<PerformanceAdviserRuleId> rulesIdsToExecute =
      new List<PerformanceAdviserRuleId>();
 
    // Iterate through each
 
    foreach( PerformanceAdviserRuleId ruleId in ruleIds )
    {
      if( adviser.GetRuleName( ruleId ).Equals( "Overlapping walls" ) )
      {
        rulesIdsToExecute.Add( ruleId );
        break;
      }
    }
 
    // Now we need to post the results
 
    IList<FailureMessage> failureMessages =
      adviser.ExecuteRules( doc, rulesIdsToExecute );
 
    foreach( FailureMessage fm in failureMessages )
    {
      Transaction trans = new Transaction( doc );
      trans.Start( "Failure Reporting" );
      doc.PostFailure( fm );
      trans.Commit();
    }
 
    return Result.Succeeded;
  }
}

Defining a Custom Rule

Now, the most powerful part of this API is that we define custom adviser rules. To create our own, we define a class implementing an interface called IPerformanceAdviserRule. Your custom adviser class then needs to define the following methods:

  • GetName – returns the name of this rule.
  • GetDescription – provides the description of this custom rule.
  • InitCheck – performs the initialization work before running the rule. For example, it could be a task like clearing a collection of elements that we will be storing elements that fail the rule execution test.
  • WillCheckElements – sets whether the rule will iterate through elements or not.
  • GetElementFilter – provides the element filter which can help narrow down the type or category of elements that a certain rule is expected to work with.
  • ExecuteElementCheck – This method contains most of the implementation steps for a given rule. It examines the element passed to it (which is filtered by the GetElementFilter) and checks for all elements that fail the criteria laid by the rule.
  • FinalizeCheck – This method gets called by the PerformanceAdviser after all the elements in a document matching the element filter in the GetElementFilter method have been checked by the ExecuteElementCheck method. This method usually will contain the code to check if there are elements which fail the rule test and if there are, the names of the elements are listed along with the standard warning failure message.

The code below shows an example of a custom performance adviser rule, which provides warnings if there are clashes between ducts in a Revit model:


// A custom performance adviser rule class.
// This rule provides a warning if the model 
// contains ducts which clash each other.  
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
using Autodesk.Revit.UI;
using Autodesk.Revit.DB;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB.Mechanical;
using System.Diagnostics;
 
namespace BuildingCoder
{
  public class DuctClashDetection
    : IPerformanceAdviserRule
  {
    private FailureDefinition m_warning;
    private FailureDefinitionId m_warningId;
    private List<ElementId> m_ClashingDucts;
 
    /// <summary>
    /// DuctClashDetection Constructor
    /// </summary>
    public DuctClashDetection()
    {
      // Create failure message
 
      m_warningId = new FailureDefinitionId(
        new Guid( "AACC9C09-7956-4B36-9D27-FF8A64544C31" ) );
 
      m_warning = FailureDefinition.CreateFailureDefinition(
        m_warningId, FailureSeverity.Warning,
        "Some ducts clash with each other" );
    }
 
    /// <summary>
    /// This method does most of the work of the 
    /// IPerformanceAdviserRule implementation. 
    /// It is called by PerformanceAdviser and 
    /// examines the element passed to it 
    /// (which was previously filtered by the filter 
    /// returned by GetElementFilter).  This method 
    /// is checking for all elements that intersect 
    /// with current duct and adds them to the list 
    /// of elements.
    /// </summary>
    public void ExecuteElementCheck(
      Document document,
      Element element )
    {
      // For each of the elements, check if there is 
      // any intersection with the specific element 
      // being passed on via the method signature
 
      FilteredElementCollector collector
        = new FilteredElementCollector( document );
 
      // Use the Elements Intersection Filter to get elements
      // that intersect with the specified element
 
      ElementIntersectsElementFilter elementFilter =
        new ElementIntersectsElementFilter( element, false );
 
      collector.WherePasses( elementFilter );
 
      // Exclude the element in the intersection
 
      List<ElementId> excludes = new List<ElementId>();
      excludes.Add( element.Id );
      collector.Excluding( excludes );
 
      // Save the intersecting ducts
 
      foreach( Element elem in collector )
      {
        m_ClashingDucts.Add( elem.Id );
      }
    }
 
    // The rule ID for this rule;
 
    public PerformanceAdviserRuleId Id =
      new PerformanceAdviserRuleId(
        new Guid( "C00CEF6D-2C4B-402C-9AED-160DFA1785BE" ) );
 
    /// <summary>
    /// This method is called by PerformanceAdviser 
    /// after all elements in document matching the 
    /// ElementFilter from GetElementFilter() are 
    /// checked by ExecuteElementCheck(). It checks to 
    /// see if there are any ducts in the 
    /// m_ClashingDucts list. If so, it iterates 
    /// through that list and reports the failures.
    /// </summary>
    public void FinalizeCheck( Document document )
    {
      try
      {
        // If no ducts clash
 
        if( m_ClashingDucts.Count == 0 )
        {
          Debug.WriteLine(
            "No clashing ducts. Test passed." );
        }
        else
        {
          // Pass the element IDs of the clashing ducts to the revit
          // failure reporting APIs.
 
          FailureMessage fm =
            new FailureMessage( m_warningId );
 
          fm.SetFailingElements( m_ClashingDucts );
 
          Transaction failureReportingTransaction =
            new Transaction( document );
 
          failureReportingTransaction.Start(
            "Failure reporting" );
 
          PerformanceAdviser.GetPerformanceAdviser()
            .PostWarning( fm );
 
          failureReportingTransaction.Commit();
 
          m_ClashingDucts.Clear();
        }
      }
      catch( System.Exception ex )
      {
        TaskDialog.Show( "Oops!", ex.Message );
      }
    }
 
    /// <summary>
    /// Return rule description.
    /// </summary>
    public string GetDescription()
    {
      return "Detects clash between ducts";
    }
 
    /// <summary>
    /// Provide the filter to focus on elements 
    /// that this rule should be focused on.
    /// </summary>
    public ElementFilter GetElementFilter(
      Document document )
    {
      return new ElementClassFilter( typeof( Duct ) );
    }
 
    /// <summary>
    /// Return name of rule.
    /// </summary>
    public string GetName()
    {
      return "Duct Clash Detection";
    }
 
    /// <summary>
    /// Perform initial/preliminary work 
    /// before executing the tests.
    /// </summary>
    public void InitCheck( Document document )
    {
      if( m_ClashingDucts == null )
        m_ClashingDucts = new List<ElementId>();
      else
        m_ClashingDucts.Clear();
 
      return;
    }
 
    /// <summary>
    /// Return true if this rule will iterate 
    /// through elements and check them, 
    /// else return false.
    /// </summary>
    public bool WillCheckElements()
    {
      return true;
    }
  }
}

Once we have defined a class for our custom rule, our next step is to register the rule. You can do this by calling PerformanceAdvisor.AddRule.
This is done at the start-up of Revit using the OnStartup event of IExternalApplication.

Similarly, we can unregister the Performance Adviser rule at the OnShutdown event. The following code shows an example of how the custom rule is registered and unregistered:


[Transaction( TransactionMode.Manual )]
public class AddPerformanceRule : IExternalApplication
{
  public DuctClashDetection ductClashDetect;
 
  /// <summary>
  /// Unregister the custom rule.
  /// </summary>
  public Result OnShutdown(
    UIControlledApplication application )
  {
    PerformanceAdviser adviser
      = PerformanceAdviser.GetPerformanceAdviser();
 
    adviser.DeleteRule( ductClashDetect.Id );
 
    return Result.Succeeded;
  }
 
  /// <summary>
  /// Register the custom rule.
  /// </summary>
  public Result OnStartup(
    UIControlledApplication application )
  {
    PerformanceAdviser adviser
      = PerformanceAdviser.GetPerformanceAdviser();
 
    ductClashDetect = new DuctClashDetection();
 
    adviser.AddRule( ductClashDetect.Id, ductClashDetect );
 
    return Result.Succeeded;
  }
}

Once the custom rule has been added, we can execute it in the same way as the predefined rules.
Using the code used in this article against a given set of clashing ducts, this new API can report the clash results as shown below:

Collision test rule

In this article, we have shown you that the performance adviser API provides the ability to define custom rules, execute them, and report potentially problematic results via the failure message APIs.
The Revit SDK PerformanceAdviserControl sample shows more comprehensive examples of this API.
It presents a list of rules for the users to select and execute.
Please refer to the sample for more details.

Many thanks to Saikat for this helpful overview!


Comments

12 responses to “The Performance Adviser API”

  1. Hi Jeremy,
    there are still many other interesting topics in the 2012 API which haven’t found their way to your blog yet.
    I would like to see more about the new Utils methods.
    If you seach for “*utils” in the 2011 RevitAPI.chm, there are 43 results; in 2012, you’ll find 265 entries.
    There are much static classes whose names end with “-Utils”, e.g. PlumbingUtils, WallUtils, FamilyUtils, HostObjectUtils, OptionalFunctionalityUtils…
    Most of them provide useful methods which someone should know before trying to implement their functionality on his own, which would be unnecessary.
    Bye,
    Rudolf

  2. Dear Rudolf,
    Yes, I fully agree with you.
    I asked around internally, and we do not have any unpublished documentation that we are holding back. The “What’s New” section in the Revit API help file RevitApi.chm is all we have.
    Apparently, you have done some little research on this already. If you would like to put together a little overview of what you found out so far, that might be useful as a starting point. Optimistically assuming that the class names have anything at all to do with their purpose, of course :-)
    Cheers, Jeremy.

  3. Hi Jeremy,
    I didn’t mean that I needed more information than I already can get from the RevitAPI.chm.
    I think the API documentation is sufficient.
    But for the readers of your blog, it could be useful if the different Utils methods would be displayed next to each other, in a table, as an additional documentation, so to speak.
    A little overview, as you said.
    Unfortunately, I don’t have time for doing that.
    Bye,
    Rudolf

  4. Dear Rudolf,
    Great idea, I fully agree. Most of those classes I have not explored yet either. I would love to do so. We are in the same boat timewise, though. Any takers?
    Do you have any bright ideas on how to extract the data for such a table?
    When I search for ‘utils’ in the Revit 2012 API help file, I see 500 results listed, and no way to copy an overview of them to a text file or any other destination outside the help window.
    Maybe it would be more efficient and less work to use Reflection instead and extract the data directly from the Revit namespaces in the API assemblies?
    Cheers, Jeremy.

  5. Hi Jeremy,
    I would just copy-paste it from the API.chm.
    You could start a new series named “Utils” in which you “present” a single *Utils class every third day or so.
    Some additional explanations, hints or code samples will be sufficient.
    Perhaps there is a particular method that must be handled with care, so you could just point out that without talking about the other methods.
    The word “Utils” then appears both in your tag cloud and in the tag list; using Google search with your blog, will help visitors to find all the *Utils stuff, too.
    By the way, what about a new namespace called “Autodesk.Revit.Utils” which contains all the *Utils classes (plus some new geometrical helper methods like an offset method for a polygon, for example)?
    That’s a suggestion to the API Dev team, in fact.
    Yeah, just start a series.
    Kean does it this way, remember his Kinect stuff.
    Best regards,
    Rudolf

  6. Dear Rudolf,
    I am very glad that you find the API documentation sufficient, that is really good news!
    I like you suggestions a lot. Somehow I suspect that Kean sold his soul and now has a couple of extra hours per day to play with Kinect, and another couple to play with F#. I’m still considering something similar, but have not taken the plunge yet. Until I do so, I don’t really feel like wading through 500 hits in the API documentation and manually extracting information from them.
    By the way, the Revit API already includes a namespace named Autodesk.Revit.Utility, providing functionality related to material assets, I believe.
    Cheers, Jeremy.

  7. Hi Jeremy,
    yes, I know that there is already a namespace named “Utility”, but as you said, it refers more to materials but it is not a namespace for common utilities.
    Talking about Kean, there is another point I want to say.
    What about porting his AutoCAD sample code to Revit ?
    There is a loose series about geometrical phenomenons, for example, about fractals or the Apollonian gasket.
    I think that AcDBLine is equivalent to ModelLine (or DetailLine), every AcGeSomething has a counterpart in the Revit geometry classes.
    As for creating solids, we are forced to use the family context (or the AVF), but nearly everything could also be performed in Revit.
    To be honest, much of the AutoCAD stuff is just playing around, but you are not forbidden to have fun when coding ;-)
    What about this:
    Firstly, let Kean do all the AutoCAD work.
    Wait some time until he has finished the code and goes to bed.
    Then, when he is asleep, grab his code and adjust it to Revit environment.
    When he wakes up, he sees a new entry in YOUR blog, too.
    Perhaps he does’nt sleep at all (so he has more time for the Kinect stuff). In this case, he will prevent you from getting his code clandestinely.
    Best regards,
    Rudolf

  8. Oh my god, cannot believe I just stumbled upon you mentioning ElementIntersectsElementFilter!!!! I went thru so much hustle to recreate this functionality from scratch :(

  9. Dear Dima,
    Yes, terrible, isn’t it? There is so much stuff in the Revit API that I have never heard or dreamed about. And obviously I cannot go searching for something if I have no idea that it exists. The same thing happens to me all the time, and I am continuously flabbergasted by new stuff that was in there all the time and I was unaware of. So you have my full sympathy :-)
    Cheers, Jeremy.

  10. Jeremy, Yes it happens all the time. I usually try to think of it as a time spent towards practicing rather than wasted time – just to avoid to feel bad about the time wasted ;) I usually just have “abuse” the search text box on your blog ;)

  11. Dear Dima,
    Yes, of course, absolutely! And that is by no means abuse, absolutely intended usage, and I wish you that you always find everything you are looking for :-)
    Cheers, Jeremy.

  12. Jeremy,
    If I make and register a custom IPerformanceAdviserRule does Revit ever automatically run this rule? Or do I always have to manualy trigger it from PerformanceAdviser?
    – Eric Anastas

Leave a Reply

Discover more from Autodesk Developer Blog

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

Continue reading