Writing Your First Autodesk Revit Model Review Plug-In R. Robert Bell Sparling CP5880 The Revit Model Review plug-in is a great tool for checking a Revit model for matching the standards your company has created. Even better, the tool can fix issues that are found with the click of a button. However, the provided set of tests is basic and may not cover all the types of model standards your company believes are important. Fortunately, there is an API to create additional tests as plug-ins to the Model Review plug-in. This class will present step-by-step instructions for creating a plugin that you can use to improve collaboration in your Revit projects. Learning Objectives At the end of this class, you will be able to: Locate the API documentation Create the appropriate class module Implement the interface methods Deploy the plug-in About the Speaker Robert is the design technology manager for Sparling, a specialty electrical engineering and technology consulting firm in the United States, headquartered in Seattle, Washington. He provides strategic direction, technical oversight, and high-level support for the Sparling enterprise design and production technology systems. He is instrumental in positioning Sparling as an industry and client leader in leveraging technology in virtual building and design. Robert has been writing code for customizing AutoCAD since the release of AutoCAD v2.5. rbell@sparling.com
Introduction The Model Review add-in provides a great way to check a model for issues without manually or visually inspecting the model. The add-in not only checks the model, but can also correct some of the issues it exposes. The developer of the tool also provided an API to create additional plug-ins for the Model Review add-in. This makes it possible to expand the functionality of the add-in to meet the needs of your company. This handout supplements the video recording of this class. Locate the API documentation The API documentation consists for a PDF and a sample Visual Studio project. Both are located in the following folder for Revit 2012 installations: Folder Location 64-bit operating systems %ProgramFiles(x86)%\Autodesk\Autodesk Revit Model Review 2012\Plugin-Source 32-bit operating systems %ProgramFiles%\Autodesk\Autodesk Revit Model Review 2012\Plugin-Source Developing_ModelReview_Plugins.pdf After an introduction, the PDF, titled "Developing Plug-in Checks for Autodesk Revit Model Review", provides an overview of the methods that can be implemented in the plug-in by an interface to the Model Review add-in. It goes on to describe the procedure for creating a new Model Review plug-in. There is an explanation of the filtering mechanism used by the Model review add-in which the plug-in can initialize. The structure of the data passed between the addin and the plug-in is documented in a table. Finally, the methods in the interface are described. Sample Visual Studio Project The sample project (located in a subfolder named SamplePlugin in the folder noted above) will not run without modification in Revit 2012. It was written for earlier versions of Revit and was not updated for Revit 2012. Therefore the target framework is incorrect, references need to be corrected, and the transaction model has changed. Therefore, this class is presented to provide a working plug-in for Revit 2012. Create the appropriate class module The "Developing Plug-in Checks" documentation outlines the procedure for creating a new Model Review plug-in and is as follows: Procedure 1. Create a new Visual Studio Class Library Project 2
2. Add a reference to the RevitAPI.dll, located in Revit's Program folder. a. A reference to RevitAPIUI.dll is not required, but needed if you plan on using the Revit UI. 3. Add a reference to ModelReviewPlugins.dll, located in the main Autodesk Revit Model Review folder. 4. Create a class to implement the check 5. Add the IPluginCheck interface to the class 6. Implement the methods of the interface (see below) 7. Build the project 8. Copy the resulting DLL to either the Plugins subfolder located in the main Autodesk Revit Model Review folder or in an alternate folder as specified in the add-in interface. After completing the procedure with a successful build, the new plug-in will be available in the add-in Manage interface's menu under Check> Add> Plugins. Implement the interface methods There are five methods in the IPluginCheck interface. Two are required to be implemented. The other three add functionality and therefore are often implemented. Initialize public void Initialize(ICheckData data) This is a required method. The method initializes information about the check. Note that a PluginGUID should be provided and you should not replicate an existing GUID in a new plug-in. Configure public void Configure(System.Windows.Forms.IWin32Window window, ICheckData data) This is an optional method. Use it to gather configuration information from the user when the check needs to support user configuration. GetReportVariables public List<ReportVariable> GetReportVariables() This is an optional method. This method allows you to create variables that will be used in the report. The add-in's configuration tool uses this method. RunCheck public void RunCheck(ICheckData data) This is a required method. It performs the check, determines if the checks passes or fails, and provides supporting data. There is a typical process that should be followed by the code implementing this method: 1. Retrieve configuration data 2. Retrieve a list of objects (the filtering mechanism can reduce the number of objects) 3. Determine pass or fail 3
4. Build a report 5. Build a results tree 6. Store results CorrectCheck public void CorrectCheck(System.Windows.Forms.IWin32Window window, ICheckData data) This is an optional method. It is called when the user clicks on the Correct button. It should change the Revit model so that the bad objects are corrected and the objects would now pass a check. Note that this correction is running in a modeless application so it is mandatory to use transactions. Also, never store objects directly, instead store object IDs. Finally, never assume the object exists; always check for a valid object. After the correction is complete the plug-in check is automatically run so there is no need to reset the results in this method. Deploy the plug-in Check files that use a plug-in still need to be added to the Model Review check file list. This is done by using the add-in Manage interface's menu, Profile> Edit> Standards> Add. This edits a file in the main Autodesk Revit Model Review folder called ModelReview.config, which is an XML-based file. This folder is read-only to normal users so either Revit needs to be run as an Administrator to use the interface to add the check file, or the file needs to be externally edited and replaced using administrative privileges. Note that the location of the plugin DLL does not need to be in the main Autodesk Revit Model Review folder structure when an alternate location is specified in the Manage interface so the plugin could be located in a separate folder where the user has modify rights. But this still does not alleviate the read-only issue with the configuration file. Conclusion This working project, along with the API documentation, should give you the confidence to develop your own plug-ins to expand the functionality of Autodesk's Revit Model Review add-in. 4
Code Samples Main.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Forms; using BIMreviewPlugins; using Autodesk.Revit.ApplicationServices; using Autodesk.Revit.UI; using Autodesk.Revit.DB; using Autodesk.Revit.DB.Architecture; using Autodesk.Revit.DB.Structure; using Autodesk.Revit.DB.Mechanical; using Autodesk.Revit.DB.Electrical; using Autodesk.Revit.DB.Plumbing; namespace Sparling_Model_Review_Plug_In public class Reference_Planes_On_Specified_Workset: IPluginCheck #region Declarations private string _worksetname; private int _worksetid; private IList<Workset> _worksets; #endregion #region PrivateMethods private string buildbadelementtable (List<ReferencePlaneResult> badelements) // make an HTML table for the report StringBuilder sb = new StringBuilder(); sb.appendline("<table border='1'>"); sb.appendline(string.format( "<tr><th>0</th><th>1</th><th>2</th></tr>", CommonResources.String_ID, CommonResources.String_Type, CommonResources.String_Problem)); for (int i = 0; i < badelements.count; i++) string prob = string.format( "0 " + CommonResources.String_Is + " 1 " + CommonResources.String_AndNot + " 2", CommonResources.String_Workset, badelements[i].worksetname, _worksetname); sb.appendformat( "<tr><td>0</td><td>1</td><td>2</td></tr>", badelements[i].id, CommonResources.String_ReferencePlane, prob); sb.appendline("</table>"); return sb.tostring(); private TreeNode buildelementtree(list<referenceplaneresult> badelements, ICheckData data) TreeNode node = new TreeNode(CommonResources.String_ReferencePlanes); foreach (ReferencePlaneResult e in badelements) TreeNode elementnode = new TreeNode(string.Format( "0 (1=2)", e.name, CommonResources.String_ID, e.id.tostring())); 5
// use the Tag to store whatever data you want // (recommend the ID or UniqueID for an element) // this will be used later for highlighting, etc. elementnode.tag = e.id; node.nodes.add(elementnode); return node; private IList<Workset> getworksets (ICheckData data) FilteredWorksetCollector worksetscollector = new FilteredWorksetCollector(data.RevitDocument); worksetscollector.ofkind(worksetkind.userworkset); IList<Workset> worksets = worksetscollector.toworksets(); return worksets; private WorksetId getworksetid (string wsname) WorksetId wsid = null; foreach (Workset workset in _worksets) if (workset.name == wsname) wsid = workset.id; _worksetid = workset.id.integervalue; return wsid; private string getworksetname (int chkid) string wsname = string.empty; foreach (Workset workset in _worksets) if (workset.id.integervalue == chkid) wsname = workset.name; return wsname; #endregion #region IPluginCheck Members public void Initialize(ICheckData data) // set up basic check information data.filter = CheckInfo.FilterType.Elements; data.filtersubtypes = new string[1] CommonResources.String_RevitReferencePlane; data.isconfigurable = true; data.correctable = true; data.supportsfamilydocuments = false; // generate your own GUID and place it here data.pluginguid = "90AE4CA5-9FDE-4837-BA68-420F07A0768E"; // initialize configuration data in case someone runs it without configuration if (data.configdata == null) data.configdata = CommonResources.String_SharedLevelsGrids; _worksetname = data.configdata; // fail/pass messages data.failmessage = "<B>%CheckName%: %CheckResult%</B><BR/><BR/><P>" + CommonResources.String_ThereWere + " NumberOfBadElements " + CommonResources.String_SomePlanesFail + "</P>BadElementTable"; data.passmessage = "<B>%CheckName%: %CheckResult%</B><BR/><BR/><P>" + CommonResources.String_AllPlanesOK +"</P>"; 6
public void Configure(System.Windows.Forms.IWin32Window window, ICheckData data) // get previous configuration data if (data.configdata!= null) _worksetname = data.configdata; SpecifyWorksetForm cfgform = new SpecifyWorksetForm(); cfgform.selectedworkset = _worksetname; if (cfgform.showdialog(window) == DialogResult.OK) // store configuration as string to be persisted in the check file data.configdata = cfgform.selectedworkset; _worksetname = data.configdata; public List<ReportVariable> GetReportVariables() // fill in the report variables, if any List<ReportVariable> vars = new List<ReportVariable>(); vars.add( new ReportVariable( ReportVariable.VariableTypeEnum.Value, "NumberOfBadElements" ) ); vars.add( new ReportVariable( ReportVariable.VariableTypeEnum.Table, "BadElementTable" ) ); return vars; public void RunCheck(ICheckData data) // unpack the config data _worksetname = data.configdata; // get workset ID of target workset _worksets = getworksets(data); WorksetId wsid = getworksetid(_worksetname); // create a structure to store the bad results List<ReferencePlaneResult> badelements = new List<ReferencePlaneResult>(); // check only when target workset is found if (wsid!= null) // the filterdata contains the objects to check (if appropriate) if (data.filterdata!= null) foreach (object obj in data.filterdata) ReferencePlane myref = obj as ReferencePlane; if (myref!= null) // test the reference plane Parameter wsval = myref.get_parameter("workset"); if (wsval == null) continue; if (wsval.asinteger()!= wsid.integervalue) ReferencePlaneResult result = new ReferencePlaneResult(myRef); result.worksetname = getworksetname(wsval.asinteger()); badelements.add(result); else MessageBox.Show(CommonResources.String_The + " " + _worksetname + " " + CommonResources.String_WorksetNotFound, CommonResources.String_UnneededCheckTitle, MessageBoxButtons.OK, MessageBoxIcon.Information); data.result = CheckInfo.ResultType.Passed; 7
// check status if (badelements.count == 0) data.result = CheckInfo.ResultType.Passed; else data.result = CheckInfo.ResultType.Failed; // store the bad result to be returned later for correction data.resultdata = badelements; // build the replaceable tokens for the report data.reporttokens = new Dictionary<string, string>(); data.reporttokens.add("numberofbadelements", badelements.count.tostring()); data.reporttokens.add("badelementtable", buildbadelementtable(badelements)); // populate the node tree data.resultstree = buildelementtree(badelements, data); public void CorrectCheck(System.Windows.Forms.IWin32Window window, ICheckData data) // go thru each of the reference planes List<ReferencePlaneResult> warnings = data.resultdata as List<ReferencePlaneResult>; if (warnings == null) return; // we need to do this in a transaction Transaction trans = new Transaction(data.RevitDocument); trans.start("fixreferenceplaneworkset"); foreach (ReferencePlaneResult result in warnings) // get the element Autodesk.Revit.DB.ElementId eid = new Autodesk.Revit.DB.ElementId(result.Id); Element e = data.revitdocument.get_element(eid); if (e!= null) // get the parameter Parameter p = e.get_parameter("workset"); if (p!= null) p.set(_worksetid); trans.commit(); #endregion ReferencePlaneResult.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Sparling_Model_Review_Plug_In /// <summary> /// Simple class to represent a failed reference plane result /// </summary> class ReferencePlaneResult public string Name; public int Id; public string WorksetName; public ReferencePlaneResult(Autodesk.Revit.DB.Element e) Name = e.name; Id = e.id.integervalue; 8
SpecifyWorksetForm.cs using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; namespace Sparling_Model_Review_Plug_In public partial class SpecifyWorksetForm : Form private string _selectedworkset; public string SelectedWorkset get return _selectedworkset; set _selectedworkset = value; public SpecifyWorksetForm() InitializeComponent(); private bool ValidateInput() if (inpworksetname.text.trim() == string.empty) MessageBox.Show(this, CommonResources.String_SpecifyWorkset, CommonResources.String_SpecifyWorksetTitle); inpworksetname.focus(); return false; return true; private void SpecifyWorksetForm_Load(object sender, EventArgs e) this.text = CommonResources.String_SpecifyWorksetTitle; label1.text = CommonResources.String_SpecifyWorksetLabel; inpok.text = CommonResources.String_OK; inpcancel.text = CommonResources.String_Cancel; inpworksetname.text = _selectedworkset; private void inpok_click(object sender, EventArgs e) if (ValidateInput() == true) _selectedworkset = inpworksetname.text.trim(); this.dialogresult = DialogResult.OK; this.close(); CommonResources String_AllPlanesOK All reference planes met the criteria of this check. String_AndNot and not String_Cancel Cancel String_ID ID String_Is is String_OK OK String_Problem Problem String_ReferencePlane Reference Plane String_ReferencePlanes Reference Planes String_RevitReferencePlane ReferencePlane String_SelectWorkset Select Workset String_SharedLevelsGrids Shared Levels and Grids String_SomePlanesFail reference planes which did not meet the criteria of this check. 9
String_SpecifyWorkset You must specify a workset name. String_SpecifyWorksetLabel Specify the Workset that Reference Planes should be on: String_SpecifyWorksetTitle Specify Workset String_The The String_ThereWere There were String_Type Type String_UnneededCheckTitle Unneeded Check String_Workset Workset String_WorksetNotFound workset cannot be found. Form Image 10