RevitToolkit
repository·main·Indexed 41 days ago
https://github.com/nice3point/revittoolkitA library providing a modern and flexible interface for the Revit API. It offers abstractions for common development tasks, including synchronous and asynchronous External Commands (AsyncExternalCommand), External Applications, and External Events. Features include a fluent API for selection filters via SelectionConfiguration, failure and dialog suppression scopes, and an attribute-based generator for External Events. Includes Roslyn analyzer rules in version 2027.0.0 to ensure correct implementation of generated events.
What's inside RevitToolkit
- RevitToolkit provides a modern interface for the Revit API. It includes implementations for common interfaces encountered during Revit development, allowing developers to choose specific components to improve flexibility and reduce boilerplate.
Use External Events for modeless windows
mainThe toolkit provides several
ExternalEventimplementations to allow executing Revit API code from outside the Revit execution context (e.g., from a modeless window or a different thread). The toolkit handles all initialization automatically.ExternalEvent: A synchronous event. CallingRaise()queues the delegate to be executed in the next Revit event-processing cycle.ExternalEvent<T>: A synchronous event that accepts an argument of typeTviaRaise(T arg).AsyncExternalEvent: An asynchronous event.RaiseAsync()returns aTaskthat completes when Revit finishes the delegate. Warning: Do not use.Wait()or.Resulton the Revit main thread as it will cause a deadlock; always useawait.AsyncExternalEvent<T>: An asynchronous event that accepts an argument of typeT.AsyncRequestExternalEvent<TResult>: An asynchronous event that returns a result of typeTResultviaRaiseAsync().AsyncRequestExternalEvent<T, TResult>: A generic asynchronous event that accepts an argumentTand returnsTResult.
// AsyncRequestExternalEvent<T, TResult> Example private readonly AsyncRequestExternalEvent<ElementId, bool> _deleteWindowRequestEvent = new((application, elementId) => { var document = application.ActiveUIDocument.Document; using var transaction = new Transaction(document, "Delete window"); transaction.Start(); document.Delete(elementId); transaction.Commit(); return true; }); private async Task DeleteWindowAsync(ElementId elementId) { var result = await _deleteWindowRequestEvent.RaiseAsync(elementId); }Generate External Events with the [ExternalEvent] attribute
mainThe
[ExternalEvent]attribute eliminates boilerplate by automatically generating event properties for annotated methods in apartialclass. The generator creates properties based on the method signature:voidmethod: GeneratesIExternalEventandIAsyncExternalEventproperties.- Method with return value: Generates an
IAsyncRequestExternalEvent<TResult>property. - Method with
UIApplicationparameter: The generated events pass theUIApplicationinstance to the handler. - Method with one extra parameter: Generates typed event properties (e.g.,
IExternalEvent<T>). - Method with multiple extra parameters: Generates a
sealed recordcontaining the arguments and extension methods to callRaiseAsyncusing individual parameters.
Use
[ExternalEvent(AllowDirectInvocation = true)]to allow the handler to be invoked directly on the calling thread when Revit is in API mode.public partial class MyViewModel { [ExternalEvent] private void DeleteWindow(UIApplication application, ElementId elementId) { // Implementation } } // Generates: // IExternalEvent<ElementId> DeleteWindowEvent // IAsyncExternalEvent<ElementId> DeleteWindowAsyncEventInstall RevitToolkit via NuGet
mainRevitToolkit can be installed as a NuGet package. Because the packages are compiled for specific Revit versions, you should use the
RevitVersionproperty to manage version compatibility in your project file.<PackageReference Include="Nice3point.Revit.Toolkit" Version="$(RevitVersion).*"/>Use JournalData for non-interactive command execution
mainThe
JournalDataproperty inExternalCommandprovides a way to pass data into your command during Revit journal playback. This allows you to implement commands that can run in a UI-less mode, which is essential for automated testing and non-interactive automation.- Writing Data: You can store string-to-string data in the
JournalDatadictionary at the end of your command execution. This data is then recorded in the Revit journal file. - Reading Data: When the command is executed via journal playback, the data stored in the journal is passed back into the
JournalDataproperty of your command instance.
- Writing Data: You can store string-to-string data in the
Manage Revit API Context and Failure Suppression
mainThe toolkit provides context objects to access global application information and manage Revit's behavior using disposable scopes.
RevitApiContext(Database Level)BeginFailureSuppressionScope(): Automatically resolves all failures without user interaction. UseresolveErrors: falseto cancel the transaction and undo all failed changes if a failure occurs.
RevitContext(UI Level)- Properties:
UiApplication,ActiveDocument,ActiveUiDocument,ActiveView,ActiveGraphicalView,IsRevitInApiMode. BeginDialogSuppressionScope(): Suppresses dialogs. You can specify aresultCode(e.g.,TaskDialogResult.Ok) or provide a custom handler to decide the result based on theDialogId.
// Suppress failures and undo on error using (RevitApiContext.BeginFailureSuppressionScope(resolveErrors: false)) { ModifyDocument(); } // Suppress dialogs with a custom handler using (RevitContext.BeginDialogSuppressionScope(args => { var result = args.DialogId == "TaskDialog_ModelUpdater" ? TaskDialogResult.Ok : TaskDialogResult.Close; args.OverrideResult((int)result; })) { LoadFamilies(); }Resolve dependencies with ResolveHelper
mainThe
ResolveHelperprovides scoped assembly resolution to ensure dependencies are found. This is useful when loading external libraries from specific paths.BeginAssemblyResolveScope<T>(): Resolves dependencies for a specific type.BeginAssemblyResolveScope(string path): Searches for dependencies in a specific directory.- Nesting: Scopes can be nested; dependencies are searched from the innermost scope to the outermost.
```c# using (ResolveHelper.BeginAssemblyResolveScope(@Implement asynchronous External Commands
mainUse the
AsyncExternalCommandbase class forasync/awaitsupport. OverrideExecuteAsync()to perform I/O-bound operations (like HTTP requests or database queries) without freezing the Revit UI. The toolkit uses dispatcher message pumping to keep the UI responsive.[Transaction(TransactionMode.Manual)] public class Command : AsyncExternalCommand { public override async Task ExecuteAsync() { using var httpClient = new HttpClient(); var response = await httpClient.GetStringAsync("https://example.com"); using var transaction = new Transaction(Application.ActiveUIDocument.Document, "Update Parameter"); transaction.Start(); var element = Application.ActiveUIDocument.Document.GetElement(id); element?.FindParameter(BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS)?.Set(response); transaction.Commit(); } }Implement External Applications
mainThe toolkit provides base classes for Revit external applications with simplified method signatures and automatic dependency resolution.
ExternalApplication: ImplementIExternalApplication. OverrideOnStartup()and optionallyOnShutdown().AsyncExternalApplication: Implement asynchronous startup/shutdown. OverrideOnStartupAsync()and optionallyOnShutdownAsync(). Ideal for I/O-bound tasks during startup.ExternalDBApplication: ImplementIExternalDBApplication. Use this when you do not need access to the Revit UI.
public class Application : AsyncExternalApplication { public override async Task OnStartupAsync() { using var httpClient = new HttpClient(); var configuration = await httpClient.GetStringAsync("https://api.example.com/config"); var panel = Application.CreatePanel(configuration.PanelTitle); panel.AddPushButton<Command>(configuration.ButtonTitle); } public override async Task OnShutdownAsync() { await SaveSettingsAsync(); } }Configure Selection Filters with SelectionConfiguration
mainThe
SelectionConfigurationclass implementsISelectionFilterand provides a fluent API to define what elements or references are selectable during aPickObjectoperation.- Default: All elements are allowed.
- Customization: Use
.Allow.Element(predicate)to filter elements and.Allow.Reference(predicate)to filter references.
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);Implement synchronous External Commands
mainUse the
ExternalCommandbase class to implementIExternalCommand. Instead of the full interface, you only need to override theExecute()method. The toolkit automatically handles dependency resolution to preventFileNotFoundExceptionby searching the plugin folder.[Transaction(TransactionMode.Manual)] public class Command : ExternalCommand { public override void Execute() { var document = Application.ActiveUIDocument.Document; } }Review shipped analyzer rules in Release 2027.0.0
mainThe
Nice3point.Revit.Toolkit.Analyzerspackage includes specific Roslyn analyzer rules for theExternalEventGeneratorto ensure correct implementation of Revit external events. In version2027.0.0, the following rules are enforced:Rule ID Category Severity Description RVTTK0001ExternalEventGenerator Error Method must return TaskorTask<T>RVTTK0002ExternalEventGenerator Warning Method should not be async voidRVTTK0003ExternalEventGenerator Error Method cannot be generic RVTTK0004ExternalEventGenerator Error Duplicate method overloads are not allowed RVTTK0005ExternalEventGenerator Error The containing type must be marked as partial| Rule ID | Category | Severity | Notes | |------------|------------------------|----------|--------------------------------| | RVTTK0001 | ExternalEventGenerator | Error | Method returns Task or Task<T> | | RVTTK0002 | ExternalEventGenerator | Warning | Method is async void | | RVTTK0003 | ExternalEventGenerator | Error | Method is generic | | RVTTK0004 | ExternalEventGenerator | Error | Duplicate method overloads | | RVTTK0005 | ExternalEventGenerator | Error | Containing type is not partial |