Top 10 Revit API Tips Every Developer Should Know
Master essential Revit API techniques that will dramatically improve your add-in development workflow and code quality.
Top 10 Revit API Tips Every Developer Should Know
Building Revit add-ins can transform how architectural and engineering firms work, automating repetitive tasks and unlocking powerful customization capabilities. However, mastering the Revit API requires understanding its unique architecture and best practices. This comprehensive guide shares ten essential tips that will accelerate your development journey and help you avoid common pitfalls.
Understanding the Revit API Architecture
The Revit API is built on the .NET Framework, providing developers with access to nearly every aspect of the Revit application through managed code. Unlike traditional software APIs that follow REST or GraphQL patterns, the Revit API operates as an in-process extension, meaning your code runs within the same process as Revit itself. This architecture provides exceptional performance but requires careful attention to threading, transactions, and object lifecycle management.
The Transaction Model forms the foundation of all Revit API operations. Every modification to the Revit document must occur within a transaction, which ensures data integrity and enables the undo/redo functionality that users expect. Transactions group related changes together, allowing Revit to maintain its parametric relationships and update all dependent elements automatically.
Tip 1: Master the Transaction Lifecycle
Understanding when and how to use transactions is crucial for successful Revit API development. Transactions must be started before making any changes to the document and committed to persist those changes. Failing to properly manage transactions leads to exceptions and unpredictable behavior.
using (Transaction trans = new Transaction(doc, "Create Wall"))
{
trans.Start();
// Create a wall
Level level = new FilteredElementCollector(doc)
.OfClass(typeof(Level))
.FirstElement() as Level;
Line line = Line.CreateBound(new XYZ(0, 0, 0), new XYZ(10, 0, 0));
Wall wall = Wall.Create(doc, line, level.Id, false);
trans.Commit();
}
using (Transaction trans = new Transaction(doc, "Create Wall"))
{
trans.Start();
// Create a wall
Level level = new FilteredElementCollector(doc)
.OfClass(typeof(Level))
.FirstElement() as Level;
Line line = Line.CreateBound(new XYZ(0, 0, 0), new XYZ(10, 0, 0));
Wall wall = Wall.Create(doc, line, level.Id, false);
trans.Commit();
}
The using statement ensures the transaction is properly disposed even if an exception occurs. Always wrap transaction code in try-catch blocks to handle errors gracefully and roll back changes when necessary.
Tip 2: Use Filtered Element Collectors Efficiently
The FilteredElementCollector class provides the primary mechanism for querying elements in a Revit document. However, inefficient collector usage can severely impact add-in performance, especially in large models containing hundreds of thousands of elements.
Apply filters early in the collector chain to minimize the number of elements processed. Quick filters execute at the database level and are significantly faster than slow filters that require full element instantiation. Class filters and category filters are quick filters, while parameter filters are slow filters.
// Efficient: Apply quick filters first
FilteredElementCollector collector = new FilteredElementCollector(doc)
.OfClass(typeof(Wall))
.OfCategory(BuiltInCategory.OST_Walls)
.WhereElementIsNotElementType();
// Less efficient: Iterating all elements
foreach (Element elem in new FilteredElementCollector(doc))
{
if (elem is Wall wall && !wall.IsElementType)
{
// Process wall
}
}
// Efficient: Apply quick filters first
FilteredElementCollector collector = new FilteredElementCollector(doc)
.OfClass(typeof(Wall))
.OfCategory(BuiltInCategory.OST_Walls)
.WhereElementIsNotElementType();
// Less efficient: Iterating all elements
foreach (Element elem in new FilteredElementCollector(doc))
{
if (elem is Wall wall && !wall.IsElementType)
{
// Process wall
}
}
The first approach filters at the database level, while the second instantiates every element in the document before checking its type. In a model with 50,000 elements, this difference can mean the gap between a one-second operation and a thirty-second operation.
Tip 3: Understand Element IDs and UniqueIds
Revit provides two types of identifiers for elements: ElementId and UniqueId. Understanding when to use each is essential for robust add-in development.
ElementId is an integer-based identifier that uniquely identifies an element within a single document session. However, ElementIds change when documents are workshared, upgraded, or exported and imported. Never store ElementIds in external databases or configuration files expecting them to remain valid across sessions.
UniqueId is a GUID-based identifier that remains stable across document sessions, worksharing, and even copy-paste operations between documents. Use UniqueIds when you need to maintain references to elements over time or across different document instances.
// Get both identifiers
ElementId id = element.Id;
string uniqueId = element.UniqueId;
// Retrieve element by UniqueId later
Element retrievedElement = doc.GetElement(uniqueId);
// Get both identifiers
ElementId id = element.Id;
string uniqueId = element.UniqueId;
// Retrieve element by UniqueId later
Element retrievedElement = doc.GetElement(uniqueId);
Tip 4: Leverage Parameter Access Methods
Revit elements store data in parameters, which can be built-in parameters defined by Revit or custom shared parameters added by users. Accessing parameters efficiently requires understanding the different access methods available.
Built-in parameters should be accessed using the BuiltInParameter enumeration rather than parameter names, as names can vary based on Revit language settings. The get_Parameter method with a BuiltInParameter argument provides the fastest and most reliable access.
// Preferred: Access by BuiltInParameter
Parameter commentParam = wall.get_Parameter(BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS);
// Avoid: Access by name (language-dependent)
Parameter commentParam = wall.LookupParameter("Comments");
// For shared parameters, use Definition
Definition def = doc.ParameterBindings.get_Item(sharedParamDef);
Parameter sharedParam = wall.get_Parameter(def);
// Preferred: Access by BuiltInParameter
Parameter commentParam = wall.get_Parameter(BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS);
// Avoid: Access by name (language-dependent)
Parameter commentParam = wall.LookupParameter("Comments");
// For shared parameters, use Definition
Definition def = doc.ParameterBindings.get_Item(sharedParamDef);
Parameter sharedParam = wall.get_Parameter(def);
Always check if a parameter exists before attempting to read or write its value, as not all parameters are available on all element types.
Tip 5: Handle Geometry Extraction Properly
Extracting geometry from Revit elements is a common requirement for analysis, export, and visualization tasks. The Revit API provides the GeometryElement class for accessing element geometry, but proper options configuration is essential for obtaining the desired level of detail.
Options options = new Options();
options.DetailLevel = ViewDetailLevel.Fine;
options.IncludeNonVisibleObjects = false;
options.ComputeReferences = true;
GeometryElement geomElem = element.get_Geometry(options);
foreach (GeometryObject geomObj in geomElem)
{
if (geomObj is Solid solid && solid.Volume > 0)
{
foreach (Face face in solid.Faces)
{
// Process face geometry
Mesh mesh = face.Triangulate();
}
}
else if (geomObj is GeometryInstance instance)
{
// Handle family instances
GeometryElement instGeom = instance.GetInstanceGeometry();
}
}
Options options = new Options();
options.DetailLevel = ViewDetailLevel.Fine;
options.IncludeNonVisibleObjects = false;
options.ComputeReferences = true;
GeometryElement geomElem = element.get_Geometry(options);
foreach (GeometryObject geomObj in geomElem)
{
if (geomObj is Solid solid && solid.Volume > 0)
{
foreach (Face face in solid.Faces)
{
// Process face geometry
Mesh mesh = face.Triangulate();
}
}
else if (geomObj is GeometryInstance instance)
{
// Handle family instances
GeometryElement instGeom = instance.GetInstanceGeometry();
}
}
Geometry instances represent families and groups, requiring recursive processing to extract their transformed geometry. Always check solid volumes before processing, as Revit often returns empty solids that should be filtered out.
Tip 6: Implement Proper Error Handling
Revit API operations can fail for numerous reasons: invalid transactions, locked elements, insufficient permissions, or constraint violations. Robust error handling prevents add-in crashes and provides users with meaningful feedback.
try
{
using (Transaction trans = new Transaction(doc, "Modify Elements"))
{
trans.Start();
// Perform operations
trans.Commit();
}
}
catch (Autodesk.Revit.Exceptions.ArgumentException ex)
{
TaskDialog.Show("Error", $"Invalid argument: {ex.Message}");
}
catch (Autodesk.Revit.Exceptions.InvalidOperationException ex)
{
TaskDialog.Show("Error", $"Operation not allowed: {ex.Message}");
}
catch (Exception ex)
{
TaskDialog.Show("Unexpected Error", ex.ToString());
}
try
{
using (Transaction trans = new Transaction(doc, "Modify Elements"))
{
trans.Start();
// Perform operations
trans.Commit();
}
}
catch (Autodesk.Revit.Exceptions.ArgumentException ex)
{
TaskDialog.Show("Error", $"Invalid argument: {ex.Message}");
}
catch (Autodesk.Revit.Exceptions.InvalidOperationException ex)
{
TaskDialog.Show("Error", $"Operation not allowed: {ex.Message}");
}
catch (Exception ex)
{
TaskDialog.Show("Unexpected Error", ex.ToString());
}
The Revit API defines specific exception types for different failure scenarios. Catching specific exceptions allows you to provide targeted error messages and recovery options.
Tip 7: Use External Events for Modeless Dialogs
Revit's API context restrictions prevent direct document modification from modeless dialogs or background threads. The ExternalEvent mechanism provides the solution, allowing you to queue operations that execute within a valid Revit API context.
public class MyExternalEventHandler : IExternalEventHandler
{
public void Execute(UIApplication app)
{
UIDocument uidoc = app.ActiveUIDocument;
Document doc = uidoc.Document;
using (Transaction trans = new Transaction(doc, "External Event"))
{
trans.Start();
// Perform document modifications
trans.Commit();
}
}
public string GetName()
{
return "My External Event";
}
}
// Create and raise the event
ExternalEvent exEvent = ExternalEvent.Create(new MyExternalEventHandler());
exEvent.Raise();
public class MyExternalEventHandler : IExternalEventHandler
{
public void Execute(UIApplication app)
{
UIDocument uidoc = app.ActiveUIDocument;
Document doc = uidoc.Document;
using (Transaction trans = new Transaction(doc, "External Event"))
{
trans.Start();
// Perform document modifications
trans.Commit();
}
}
public string GetName()
{
return "My External Event";
}
}
// Create and raise the event
ExternalEvent exEvent = ExternalEvent.Create(new MyExternalEventHandler());
exEvent.Raise();
This pattern enables responsive user interfaces that don't block Revit while performing long-running operations or waiting for user input.
Tip 8: Optimize Family Instance Creation
Creating multiple family instances is a common operation in generative design and automation workflows. Using the NewFamilyInstance method efficiently can significantly improve performance.
// Load family symbol
FilteredElementCollector collector = new FilteredElementCollector(doc)
.OfClass(typeof(FamilySymbol))
.OfCategory(BuiltInCategory.OST_Furniture);
FamilySymbol symbol = collector.FirstElement() as FamilySymbol;
using (Transaction trans = new Transaction(doc, "Create Instances"))
{
trans.Start();
// Activate symbol before use
if (!symbol.IsActive)
symbol.Activate();
// Create multiple instances
for (int i = 0; i < 100; i++)
{
XYZ location = new XYZ(i * 10, 0, 0);
FamilyInstance instance = doc.Create.NewFamilyInstance(
location, symbol, level, StructuralType.NonStructural);
}
trans.Commit();
}
// Load family symbol
FilteredElementCollector collector = new FilteredElementCollector(doc)
.OfClass(typeof(FamilySymbol))
.OfCategory(BuiltInCategory.OST_Furniture);
FamilySymbol symbol = collector.FirstElement() as FamilySymbol;
using (Transaction trans = new Transaction(doc, "Create Instances"))
{
trans.Start();
// Activate symbol before use
if (!symbol.IsActive)
symbol.Activate();
// Create multiple instances
for (int i = 0; i < 100; i++)
{
XYZ location = new XYZ(i * 10, 0, 0);
FamilyInstance instance = doc.Create.NewFamilyInstance(
location, symbol, level, StructuralType.NonStructural);
}
trans.Commit();
}
Always activate family symbols before creating instances, and batch multiple instance creations within a single transaction to minimize overhead.
Tip 9: Work with Views and View Templates
Views control how elements appear in Revit, and programmatically managing views is essential for documentation automation. View templates provide a powerful mechanism for standardizing view settings across projects.
// Create a new 3D view
using (Transaction trans = new Transaction(doc, "Create 3D View"))
{
trans.Start();
ViewFamilyType vft = new FilteredElementCollector(doc)
.OfClass(typeof(ViewFamilyType))
.Cast<ViewFamilyType>()
.FirstOrDefault(x => x.ViewFamily == ViewFamily.ThreeDimensional);
View3D view3D = View3D.CreateIsometric(doc, vft.Id);
view3D.Name = "Custom 3D View";
// Apply view template
View template = new FilteredElementCollector(doc)
.OfClass(typeof(View))
.Cast<View>()
.FirstOrDefault(v => v.IsTemplate && v.Name == "My Template");
if (template != null)
view3D.ViewTemplateId = template.Id;
trans.Commit();
}
// Create a new 3D view
using (Transaction trans = new Transaction(doc, "Create 3D View"))
{
trans.Start();
ViewFamilyType vft = new FilteredElementCollector(doc)
.OfClass(typeof(ViewFamilyType))
.Cast<ViewFamilyType>()
.FirstOrDefault(x => x.ViewFamily == ViewFamily.ThreeDimensional);
View3D view3D = View3D.CreateIsometric(doc, vft.Id);
view3D.Name = "Custom 3D View";
// Apply view template
View template = new FilteredElementCollector(doc)
.OfClass(typeof(View))
.Cast<View>()
.FirstOrDefault(v => v.IsTemplate && v.Name == "My Template");
if (template != null)
view3D.ViewTemplateId = template.Id;
trans.Commit();
}
Tip 10: Debug Effectively with RevitLookup
RevitLookup is an essential open-source tool for Revit API development, providing interactive exploration of the Revit database. Install RevitLookup to inspect element properties, parameters, and relationships in real-time, dramatically reducing development time.
The tool allows you to select elements in Revit and immediately view all their properties, parameters, and related objects without writing debugging code. This interactive exploration helps you understand the Revit object model and discover the correct API calls for your specific requirements.
Conclusion
Mastering the Revit API requires understanding its unique architecture, transaction model, and best practices. These ten tips provide a foundation for building robust, performant add-ins that deliver real value to users. Start with proper transaction management and efficient element filtering, then expand your skills to geometry processing, event handling, and view manipulation.
The Revit API continues to evolve with each release, adding new capabilities and improving existing functionality. Stay current with the official Revit API documentation, participate in the Autodesk forums, and study open-source projects to continuously improve your development skills. With practice and attention to these fundamental principles, you'll build add-ins that transform how professionals work with Revit.