Creating and running a custom rule with PerformanceAdviser

As an introduction to the big bunch of code below, I think PerformanceAdviser is a misleading name that understates the power of this API feature. While the default rules do focus on performance, you can use it for a wide variety of applications, and your custom rules don’t need to have anything to do with performance.

At Revit Forum it was asked if it is possible to retrieve all current warnings from the active document. This can be done with the PerformanceAdviser, but not with the default PerformanceAdviser rules. To do this you will need to make custom rules that match the warnings that Revit creates.

For this example, I made a custom rule to detect rooms that are not in properly enclosed regions. Here are screenshots comparing the output from Revit’s standard Review Warnings dialog and the output from the macro below.

roomwarnings

I’ve tried to be generous with the comments in this code, but please post comments with your questions. Figuring it out the first time is the hardest – creating additional rules is easier.

private void Module_Startup(object sender, EventArgs e)
{
    try
    {
        // Get the one instance of PerformanceAdviser in the Application
        PerformanceAdviser pa = PerformanceAdviser.GetPerformanceAdviser();

        // Create an instance of the RoomNotEnclosed rule class
         RoomNotEnclosed roomNotEnclosed = new RoomNotEnclosed();

         // Add this roomNotEnclosed rule to the PerformanceAdviser
        pa.AddRule( roomNotEnclosed.Id, roomNotEnclosed );
    }    
    // An exception need to be caught here because compiling the module causes Module_Startup to be invoked, which calls RoomNotEnclosed()
    // which calls CreateFailureDefinition which is only allowed during Revit start-up.
    catch (Autodesk.Revit.Exceptions.ApplicationException)
    {}
}

public void RunRoomRule()
{
    Document doc = this.ActiveUIDocument.Document;
    PerformanceAdviser pa = PerformanceAdviser.GetPerformanceAdviser();
    string s = "";

    // Create a list of rules to run
    IList<PerformanceAdviserRuleId> ruleList = new List<PerformanceAdviserRuleId>();

    // Find the "Room not enclosed" rule in the set of all rules
    foreach (PerformanceAdviserRuleId ruleId in pa.GetAllRuleIds())
    {
        if (pa.GetRuleName(ruleId) == "Room not enclosed.")
        {
            // Add this rule to the list of rules to run
            ruleList.Add(ruleId);
            break;
        }
    }

    // Execute the rule and loop through every FailureMessage that results
    foreach (FailureMessage message in pa.ExecuteRules(doc,ruleList))
    {
        // for each failure get its description and the names & ids of the associated elements
        s += message.GetDescriptionText() + " Elements:" + getElementsFromList(doc, message.GetFailingElements()) + "\n\n";
    }

    if (s.Length == 0)
        s = "No warnings found";

    TaskDialog.Show("Room Warnings",s);
}

// This is the implementation of the RoomNotEnclosed. It is derived from the IPerformanceAdviserRule Interface which
// defines the methods that must be included.
public class RoomNotEnclosed : IPerformanceAdviserRule
{
      private FailureDefinition m_warning;
      private FailureDefinitionId m_warningId;

      // The id that uniquely identifies the rule
      public PerformanceAdviserRuleId Id = new PerformanceAdviserRuleId(new Guid("DB21C266-743E-4771-B783-CC49BE7F2A60"));

      // The name of the rule
      public string name = "Room not enclosed.";

      // This list will hold the ids of elements found by this rule
      private IList<ElementId> ids;

  // InitCheck runs once at the beginning of the check. If the rule checks document as a whole (which this rule doesn't), the check can be performed in this method. 
  public void InitCheck(Document document)
  {
        // Create the list of ids if it does not already exist
      if( ids == null )
        ids = new List<ElementId>();
      // Clear the list of ids if it does already exist
      else
        ids.Clear();
  }

  // GetElementFilter gets a filter that restrict the set of elements to be checked. 
  public ElementFilter GetElementFilter(Document document)
      // I want to filter for only Rooms, but it is not possible to filter on this class
      // because it is not part of Revit's native object model. Instead we must filter on
      // SpatialElement, the parent class of Room which also includes the Area and Space classes.
  {  return new ElementClassFilter(typeof(SpatialElement)); }

  // ExecuteElementCheck runs once for each element that passes the ElementFilter defined in GetElementFilter
  public void ExecuteElementCheck(Document document, Element element)
  {
      // Because of the inability to filter on the Room class, this cast and null check is used to restrict
      // the check to rooms only.
      Room room = element as Room;
      if (room != null && room.Area == 0)
          ids.Add(room.Id);
  }

  // FinalizeCheck runs once at the end of the check. 
  public void FinalizeCheck(Document document)
  {    
      if (ids.Count > 0)
      {
        // Create a new failure message
          FailureMessage fm = new FailureMessage(m_warningId);

          // Set the element ids for this failure message
          fm.SetFailingElements(ids);

          // Post a warning for this failure message
          using (Transaction t = new Transaction(document, "Failure"))
          {
              t.Start();
              PerformanceAdviser.GetPerformanceAdviser().PostWarning(fm);
             t.Commit();
          }
      }
  }

  // Return true because the rule needs to be executed on individual elements.
  // False would be used if the check applieed to the document instead of individual elements
  public bool WillCheckElements()
  { return true; }

  // This is a constructor that runs when the new object is created
  public RoomNotEnclosed()
    {
      m_warningId = new FailureDefinitionId(new Guid("E7BC1F65-781D-48E8-AF37-1136B62913F5"));
      m_warning = FailureDefinition.CreateFailureDefinition(m_warningId, FailureSeverity.Warning, name);
    }

  public string GetDescription()
  { return "This room is not enclosed. Volume, area, and perimeter will not be computed."; }

  public string GetName()
  { return name; }
}

private string getElementsFromList(Document doc, ICollection<ElementId> ids)
{
    string s = "";
    foreach (ElementId id in ids)
    {
        Element e = doc.GetElement(id);
        s += " " + e.Name + " (" + id + "),";
    }
    // A comma is being added after each element id, but we don't want that comma after the last id
    return s.TrimEnd(',');
}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s