Toolkit for Revit plugin development
$ dotnet add package Nice3point.Revit.ToolkitThis library provides a modern interface for working with the Revit API. Package contains interfaces implementation frequently encountered in revit, aiming to provide as much flexibility as possible, so developers are free to choose which components to use.
You can install the Toolkit as a NuGet package.
The packages are compiled for specific versions of Revit. To support different versions of libraries in one project, use the RevitVersion property:
<PackageReference Include="Nice3point.Revit.Toolkit" Version="$(RevitVersion).*"/>
Package included by default in Revit Templates.
The Toolkit provides base classes for Revit external commands that simplify development:
FileNotFoundException exceptions (dependencies are searched in the plugin folder)ActiveDocument, ActiveView, UiApplicationExecute() instead of implementing full interfaceImplementation for IExternalCommand. Override Execute() to implement a command:
[Transaction(TransactionMode.Manual)]
public class Command : ExternalCommand
{
public override void Execute()
{
var title = ActiveDocument.Title;
var viewName = ActiveView.Name;
var username = Application.Username;
var selection = ActiveUiDocument.Selection;
var windowHandle = UiApplication.MainWindowHandle;
}
}
Implementation for asynchronous IExternalCommand. Override ExecuteAsync() for async/await support.
Enables async/await patterns while maintaining execution on the Revit main thread. The Revit UI remains responsive during async operations through dispatcher message pumping. Ideal for I/O-bound operations such as HTTP requests, file operations, or database queries.
[Transaction(TransactionMode.Manual)]
public class Command : AsyncExternalCommand
{
public override async Task ExecuteAsync()
{
var selectedIds = ActiveUiDocument.Selection.GetElementIds();
using var httpClient = new HttpClient();
var response = await httpClient.GetStringAsync("https://example.com");
using var transaction = new Transaction(ActiveDocument, "Update Parameters");
transaction.Start();
foreach (var id in selectedIds)
{
var element = ActiveDocument.GetElement(id);
element?.get_Parameter(BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS)?.Set(response);
}
transaction.Commit();
}
}
The Toolkit provides base classes for Revit external applications that simplify development:
FileNotFoundException exceptions (dependencies are searched in the plugin folder)OnStartup()/OnShutdown() instead of implementing full interfaceImplementation for IExternalApplication. Override OnStartup() and optionally OnShutdown():
public class Application : ExternalApplication
{
public override void OnStartup()
{
var panel = Application.CreatePanel("Commands", "RevitAddin");
panel.AddPushButton<Command>("Execute");
.SetImage("/RevitAddin;component/Resources/Icons/RibbonIcon16.png");
.SetLargeImage("/RevitAddin;component/Resources/Icons/RibbonIcon32.png");
}
public override void OnShutdown()
{
}
}
Implementation for IExternalDBApplication. Same as ExternalApplication but without UI access:
public class Application : ExternalDBApplication
{
public override void OnStartup()
{
}
public override void OnShutdown()
{
}
}
The Toolkit provides implementations of IExternalEventHandler for various scenarios. These handlers are used to modify the Revit document from another thread, which is particularly useful when working with modeless windows.
A handler that provides access to modify a Revit document outside the execution context with queue support for Raise method calls.
Calling a handler in a Revit context will call it immediately, without adding it to the queue.
private readonly ElementId _elementId = new(12869);
private readonly ActionEventHandler _actionEventHandler = new();
private void DeteleElement()
{
_actionEventHandler.Raise(application =>
{
var document = application.ActiveUIDocument.Document;
using var transaction = new Transaction(document, "Delete element");
transaction.Start();
document.Delete(_elementId);
transaction.Commit();
Debug.WriteLine("Deleted");
});
Debug.WriteLine("Command completed");
}
Debug output in a Revit context:
Command completed
Deleted
Debug output outside the Revit context:
Deleted
Command completed
You can set an optional exception handler to be called when an action throws an exception:
_actionEventHandler.SetExceptionHandler(exception =>
{
Debug.WriteLine($"Error: {exception.Message}");
});
With this handler, you can queue delegates for method calls when Revit becomes available again. Unsubscribing from the Idling event occurs immediately. Suitable for cases where you need to call code when Revit receives focus. For example, to display a window after loading a family into a project.
private readonly IdlingEventHandler _idlingEventHandler = new();
private void NotifyOnIdling()
{
_idlingEventHandler.Raise(application =>
{
var view = new FamilyBrowser();
view.Show();
Debug.WriteLine("Idling");
});
Debug.WriteLine("Command completed");
}
Debug output:
Command completed
Idling
You can set an optional exception handler to be called when an action throws an exception:
_idlingEventHandler.SetExceptionHandler(exception =>
{
Debug.WriteLine($"Error: {exception.Message}");
});
With this handler, you can wait for the external event to complete. The RaiseAsync method will return to its previous context after executing the method encapsulated in the delegate. Suitable for cases where you need to maintain the sequence of code execution.
Exceptions in the delegate will not be ignored and will be rethrown in the original synchronization context.
Calling the handler in a Revit context will call it immediately without adding it to the queue and awaiting with await keyword will not cause a context switch,
and you can still call API requests in the main Revit thread.
private readonly AsyncEventHandler _asyncEventHandler = new();
private async Task DeleteDoorsAsync()
{
await _asyncEventHandler.RaiseAsync(application =>
{
var doorIds = document.GetInstanceIds(BuiltInCategory.OST_Doors);
document.Delete(doorIds);
Debug.WriteLine("Doors deleted");
});
Debug.WriteLine("Command completed");
}
Debug output:
Doors deleted
Command completed
You can pass a cancellation token to cancel the operation before it executes:
await _asyncEventHandler.RaiseAsync(application =>
{
// Action
}, cancellationToken);
With this handler, you can wait for the external event to complete with the return value from the method encapsulated in the delegate. The RaiseAsync method will return to its previous context after executing. Suitable for cases where you need to maintain the sequence of code execution.
Exceptions in the delegate will not be ignored and will be rethrown in the original synchronization context
Calling the handler in a Revit context will call it immediately without adding it to the queue and awaiting with await keyword will not cause a context switch,
and you can still call API requests in the main Revit thread.
private readonly AsyncEventHandler<int> _asyncEventHandler = new();
private async Task GetWindowsCountAsync()
{
var windowsCount = await _asyncEventHandler.RaiseAsync(application =>
{
var uiDocument = application.ActiveUIDocument;
var elementIds = uiDocument.Document.GetInstanceIds(BuiltInCategory.OST_Windows);
uiDocument.Selection.SetElementIds(elementIds);
Debug.WriteLine("Windows selected");
return elementIds.Count;
});
Debug.WriteLine($"Windows count {windowsCount}");
Debug.WriteLine("Command completed");
}
Debug output:
Windows selected
Windows count 17
Command completed
You can pass a cancellation token to cancel the operation before it executes:
var count = await _asyncEventHandler.RaiseAsync(application =>
{
// Action
return 42;
}, cancellationToken);
Interfaces to global information about an application environment.
It allows access to application-specific data, as well as up-calls for application-level operations such as dialog and failure handling.
Provides members for accessing the Revit application context at the UI level.
List of available environment properties:
RevitContext data can be accessed from any application execution location:
public void Execute()
{
RevitContext.ActiveDocument.Delete(elementId);
RevitContext.ActiveView = view;
}
If your application can run in a separate thread or use API requests in an asynchronous context, perform an IsRevitInApiMode check.
A direct API call should be used if Revit is currently within an API context, otherwise API calls should be handled by IExternalEventHandler:
public void Execute()
{
if (RevitContext.IsRevitInApiMode)
{
ModifyDocument();
}
else
{
externalEventHandler.Raise(application => ModifyDocument());
}
}
RevitContext provides access to dialog suppression using disposable scopes:
using (RevitContext.BeginDialogSuppressionScope())
{
//User operations
LoadFamilies();
}
// Dialogs are restored automatically
You can specify a result code for the suppressed dialogs:
using (RevitContext.BeginDialogSuppressionScope(resultCode: 2))
{
LoadFamilies();
}
using (RevitContext.BeginDialogSuppressionScope(TaskDialogResult.Ok))
{
LoadFamilies();
}
using (RevitContext.BeginDialogSuppressionScope(MessageBoxResult.Yes))
{
LoadFamilies();
}
Or use a custom handler for more control:
using (RevitContext.BeginDialogSuppressionScope(args =>
{
var result = args.DialogId == "TaskDialog_ModelUpdater" ? TaskDialogResult.Ok : TaskDialogResult.Close;
args.OverrideResult((int)result);
}))
{
LoadFamilies();
}
Provides members for accessing the Revit application context at the database level.
List of available environment properties:
RevitApiContext provides access to failure suppression using disposable scopes.
By default, Revit uses manual error resolution control with user interaction. RevitApiContext provides automatic resolution of all failures without notifying the user or interrupting the program:
using (RevitApiContext.BeginFailureSuppressionScope())
{
using var transaction = new Transaction(document, "Operation");
transaction.Start();
// Operations that may cause failures
transaction.Commit();
}
// Failure handling is restored automatically
By default, all errors are handled for successful completion of the transaction. However, if you want to cancel the transaction and undo all failed changes, pass false as the parameter:
using (RevitApiContext.BeginFailureSuppressionScope(resolveErrors: false))
{
//User transactions
ModifyDocument();
}
The Toolkit provides implementation of various Revit interfaces, with the possibility of customization.
Contains an implementation for IFamilyLoadOptions. Provides a handler for loading families
document.LoadFamily(fileName, new FamilyLoadOptions(), out var family);
document.LoadFamily(fileName, new FamilyLoadOptions(false, FamilySource.Project), out var family);
document.LoadFamily(fileName, UIDocument.GetRevitUIFamilyLoadOptions(), out var family);
Contains an implementation for IDuplicateTypeNamesHandler. Provides a handler of duplicate type names encountered during a paste operation.
var options = new CopyPasteOptions();
options.SetDuplicateTypeNamesHandler(new DuplicateTypeNamesHandler());
options.SetDuplicateTypeNamesHandler(new DuplicateTypeNamesHandler(args => DuplicateTypeAction.Abort));
options.SetDuplicateTypeNamesHandler(new DuplicateTypeNamesHandler(DuplicateTypeAction.UseDestinationTypes));
ElementTransformUtils.CopyElements(source, elementIds, destination, null, options);
Contains an implementation for ISaveSharedCoordinatesCallback. Provides a handler for control Revit when trying to unload or reload a Revit link with changes in shared coordinates.
var linkType = elementId.ToElement<RevitLinkType>(RevitContext.ActiveDocument);
linkType.Unload(new SaveSharedCoordinatesCallback());
linkType.Unload(new SaveSharedCoordinatesCallback(SaveModifiedLinksOptions.DoNotSaveLinks));
linkType.Unload(new SaveSharedCoordinatesCallback(type =>
{
if (type.AttachmentType == AttachmentType.Overlay) return SaveModifiedLinksOptions.SaveLinks;
return SaveModifiedLinksOptions.DoNotSaveLinks;
}));
Contains an implementation for IFrameworkElementCreator.
Creator of FrameworkElements for the dockable pane.
DockablePaneProvider.Register(application, guid, title)
.SetConfiguration(data =>
{
data.FrameworkElementCreator = new FrameworkElementCreator<DockPaneView>();
data.FrameworkElementCreator = new FrameworkElementCreator<DockPaneView>(serviceProvider);
});
Contains an implementation for ISelectionFilter. Creates a configuration for creating Selection Filters.
By default, all elements are allowed for selection:
var selectionConfiguration = new SelectionConfiguration();
uiDocument.Selection.PickObject(ObjectType.Element, selectionConfiguration.Filter);
You can also customize the selection of Element or Reference separately:
var selectionConfiguration = new SelectionConfiguration()
.Allow.Element(element => element.Category.Id.AreEquals(BuiltInCategory.OST_Walls));
uiDocument.Selection.PickObject(ObjectType.Element, selectionConfiguration.Filter);
Or set rules for everything:
var selectionConfiguration = new SelectionConfiguration()
.Allow.Element(element => element.Category.Id.AreEquals(BuiltInCategory.OST_Walls))
.Allow.Reference((reference, xyz) => false);
uiDocument.Selection.PickObject(ObjectType.Element, selectionConfiguration.Filter);
Simplified implementation of raw Revit classes
Provides access to create a new dockable pane to the Revit user interface.
DockablePaneProvider
.Register(application, new Guid(), "Dockable pane")
.SetConfiguration(data =>
{
data.FrameworkElement = new RevitAddInView();
data.InitialState = new DockablePaneState
{
MinimumWidth = 300,
MinimumHeight = 400,
DockPosition = DockPosition.Right
};
});
Provides auxiliary components
Provides handlers to resolve dependencies for Revit 2025 and older.
using (ResolveHelper.BeginAssemblyResolveScope<Application>())
{
window.Show();
}
// Assembly resolution is restored automatically
You can also pass a type directly:
using (ResolveHelper.BeginAssemblyResolveScope(typeof(ViewModel)))
{
return new Window();
}
Or specify a directory path directly:
using (ResolveHelper.BeginAssemblyResolveScope(@"C:\Libraries"))
{
return LoadExternalLibrary();
}
Scopes can be nested. Dependencies are searched from innermost to outermost scope:
using (ResolveHelper.BeginAssemblyResolveScope(@"C:\Shared\Common"))
using (ResolveHelper.BeginAssemblyResolveScope(@"C:\Plugin"))
{
// First searches in Plugin, then in Common
return new Window();
}
Enabled by default for ExternalCommand, AsyncExternalCommand, ExternalApplication and ExternalDBApplication.
Adding a button to the Revit ribbon based on the username.
IExternalApplication does not provide access to Application, but you can use the RevitApiContext class to access the environment data to get the username:
public class Application : ExternalApplication
{
public override void OnStartup()
{
var panel = Application.CreatePanel("Commands", "RevitAddin");
panel.AddPushButton<Command>("Execute");
var userName = RevitApiContext.Application.Username;
if (userName == "Administrator")
{
var panel = Application.CreatePanel("Secret Panel", "RevitAddin");
panel.AddPushButton<Command>("Execute");
}
}
}
Suppression of OnShutdown call in case of unsuccessful plugin startup:
public class Application : ExternalApplication
{
private ApplicationHosting _applicationHosting;
public override void OnStartup()
{
var isValid = LicenseManager.Validate();
if (!isValid)
{
//If Result is overridden as Result.Failed, the OnShutdown() method will not be called
Result = Result.Failed;
return;
}
//Running the plugin environment in case of successful license verification
_applicationHosting = ApplicationHosting.Run();
}
public override void OnShutdown()
{
//These methods will not be called if the license check fails on startup
_applicationHosting.SaveData();
_applicationHosting.Shutdown();
}
}
Automatic transaction management without displaying additional dialogs to the user in case of an error. Can be used to use Modal windows when errors and dialogs change modal mode to modeless:
[Transaction(TransactionMode.Manual)]
public class Command : ExternalCommand
{
public override void Execute()
{
//Suppresses all possible warnings and errors during command execution
using (RevitContext.BeginDialogSuppressionScope())
using (RevitApiContext.BeginFailureSuppressionScope())
{
//Action
var selectedIds = ActiveUiDocument.Selection.GetElementIds();
using var transaction = new Transaction(ActiveDocument);
transaction.Start("Delete elements");
ActiveDocument.Delete(selectedIds);
transaction.Commit();
}
//Normal application error and dialogs handling is restored automatically
}
}
Redirecting errors to the revit dialog box, highlighting unsuccessfully deleted elements in the model:
[Transaction(TransactionMode.Manual)]
public class Command : ExternalCommand
{
public override void Execute()
{
var selectedIds = ActiveUiDocument.Selection.GetElementIds();
try
{
//Action
using var transaction = new Transaction(ActiveDocument);
transaction.Start("Delete elements");
ActiveDocument.Delete(selectedIds);
transaction.Commit();
}
catch
{
//Redirecting errors to the Revit dialog with elements highlighting
Result = Result.Failed;
ErrorMessage = "Unable to delete selected elements";
foreach (var selectedId in selectedIds)
{
ElementSet.Insert(selectedId.ToElement(ActiveDocument));
}
}
}
}