In this concluding post on this topic, we shall look at how to define a custom rule using the Performance Adviser API.
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 are 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:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB;
using Autodesk.Revit.Attributes;
namespace PASample
{
public class DuctClashDetection : IPerformanceAdviserRule
{
private FailureDefinition m_warning;
private FailureDefinitionId m_warningId;
private List<Autodesk.Revit.DB.ElementId> m_ClashingDucts;
/// <summary>
/// Constructor for DuctClashDetection
/// </summary>
public DuctClashDetection()
{
// Create failure message
m_warningId = new FailureDefinitionId(
new Guid("AACC9C09-7956-4B36-9D27-FF8A64544C31"));
m_warning = FailureDefinition.CreateFailureDefinition(
m_warningId,
Autodesk.Revit.DB.FailureSeverity.Warning,
"Some ducts clash with each other");
}
/// <summary>
/// This method does most of the work of the
/// IPerformanceAdviserRule implementation. This method is
/// called by PerformanceAdviser. It 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>
/// <param name="document"></param>
/// <param name="element"></param>
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);
}
}
/// <summary>
/// The rule ID for this rule;
/// </summary>
public Autodesk.Revit.DB.PerformanceAdviserRuleId Id =
new Autodesk.Revit.DB.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().
///
/// This method checks to see if there are any ducts in the
/// m_ClashingDucts list. If there are, it iterates through
/// that list and reports the failures.
/// </summary>
/// <param name="document"></param>
public void FinalizeCheck(Document document)
{
try
{
// If no ducts clash
if (m_ClashingDucts.Count == 0)
System.Diagnostics.Debug.WriteLine(
"No clashing ducts. Test passed.");
else
{
// Pass the element IDs of the clashing ducts to the revit
// failure reporting APIs.
Autodesk.Revit.DB.FailureMessage fm =
new Autodesk.Revit.DB.FailureMessage(m_warningId);
fm.SetFailingElements(m_ClashingDucts);
Autodesk.Revit.DB.Transaction failureReportingTransaction =
new Autodesk.Revit.DB.Transaction(document,
"Failure reporting");
failureReportingTransaction.Start();
//document.PostFailure(fm);
PerformanceAdviser.GetPerformanceAdviser().PostWarning(fm);
failureReportingTransaction.Commit();
m_ClashingDucts.Clear();
}
}
catch (System.Exception ex)
{
TaskDialog.Show("Oops!", ex.Message);
}
}
/// <summary>
/// Get the Description of the rule
/// </summary>
/// <returns></returns>
public string GetDescription()
{
return "Detects clash between ducts";
}
/// <summary>
/// Provide the filter to focus on elements that this rule should
/// be focussed on
/// </summary>
/// <param name="document"></param>
/// <returns></returns>
public ElementFilter GetElementFilter(Document document)
{
return new ElementClassFilter(typeof(
Autodesk.Revit.DB.Mechanical.Duct));
}
/// <summary>
/// Get the name of the rule
/// </summary>
/// <returns></returns>
public string GetName()
{
return "Duct Clash Detection";
}
/// <summary>
/// Perform initial/preliminary work before executing the tests
/// </summary>
/// <param name="document"></param>
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>
/// <returns></returns>
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 startup 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;
public Result OnShutdown(UIControlledApplication application)
{
PerformanceAdviser adviser = PerformanceAdviser.GetPerformanceAdviser();
adviser.DeleteRule(ductClashDetect.Id);
return Result.Succeeded;
}
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:
In the blog posts on this topic, we have seen how 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 sample called PerformanceAdviserControl shows more comprehensive example of this API. It presents a list of rules for the users to select and execute. Please refer to the sample for more detail.