Nice3point.Revit.Toolkit 2024.3.0

Prefix Reserved
There is a newer version of this package available.
See the version list below for details.
dotnet add package Nice3point.Revit.Toolkit --version 2024.3.0
                    
NuGet\Install-Package Nice3point.Revit.Toolkit -Version 2024.3.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Nice3point.Revit.Toolkit" Version="2024.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Nice3point.Revit.Toolkit" Version="2024.3.0" />
                    
Directory.Packages.props
<PackageReference Include="Nice3point.Revit.Toolkit" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Nice3point.Revit.Toolkit --version 2024.3.0
                    
#r "nuget: Nice3point.Revit.Toolkit, 2024.3.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Nice3point.Revit.Toolkit@2024.3.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Nice3point.Revit.Toolkit&version=2024.3.0
                    
Install as a Cake Addin
#tool nuget:?package=Nice3point.Revit.Toolkit&version=2024.3.0
                    
Install as a Cake Tool

Make Revit API more flexible

Nuget Downloads Last Commit

This 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.

Installation

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.

Table of contents

Features

External Commands

The Toolkit provides base classes for Revit external commands with implemented:

  • Automatic dependency resolution to avoid FileNotFoundException exceptions (dependencies are searched in the plugin folder)
  • Simplified method signatures — override Execute() instead of implementing full interface
ExternalCommand

Implementation for IExternalCommand. Override Execute() to implement a command:

[Transaction(TransactionMode.Manual)]
public class Command : ExternalCommand
{
    public override void Execute()
    {
        var document = Application.ActiveUIDocument.Document;
    }
}
AsyncExternalCommand

Implementation for asynchronous IExternalCommand. Override ExecuteAsync() for async/await support.

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()
    {
        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();
    }
}

External Applications

The Toolkit provides base classes for Revit external applications with implemented:

  • Automatic dependency resolution to avoid FileNotFoundException exceptions (dependencies are searched in the plugin folder)
  • Simplified method signatures — override OnStartup()/OnShutdown() instead of implementing full interface
ExternalApplication

Implementation 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()
    {
    }
}
AsyncExternalApplication

Implementation for asynchronous IExternalApplication. Override OnStartupAsync() and optionally OnShutdownAsync() for async/await support.

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 during application startup and shutdown.

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();
    }
}
ExternalDBApplication

Implementation for IExternalDBApplication. Same as ExternalApplication but without UI access:

public class Application : ExternalDBApplication
{
    public override void OnStartup()
    {
    }

    public override void OnShutdown()
    {
    }
}

External events

The Toolkit provides ExternalEvent implementations for working with the Revit API from outside the execution context, which is particularly useful when working with modeless windows.

Events do not need to be created inside the Revit API context before use — the Toolkit handles all the initialization for you, so you can create them anywhere in your code and on any thread.

ExternalEvent

A synchronous external event that queues the handler via the Revit external event mechanism. When Raise() is called, the delegate is placed into the Revit event queue and will be executed in the next event-processing cycle, once Revit is ready and no other commands or edit modes are active.

private readonly ExternalEvent _deleteWindowsEvent = new(application =>
{
    var document = application.ActiveUIDocument.Document;
    using var transaction = new Transaction(document, "Delete windows");
    transaction.Start();
    document.Delete(document.GetInstanceIds(BuiltInCategory.OST_Windows));
    transaction.Commit();

    //2. The delegate body executes when Revit processes the event
});

private void DeleteWindows()
{
    _deleteWindowsEvent.Raise();

    //1. Raise returns immediately, the delegate is queued
}
ExternalEvent<T>

A generic synchronous external event that accepts an argument of type T. Works like ExternalEvent, but allows passing data to the handler at the time of raising.

private readonly ExternalEvent<ElementId> _deleteWindowEvent = new((application, elementId) =>
{
    var document = application.ActiveUIDocument.Document;
    using var transaction = new Transaction(document, "Delete window");
    transaction.Start();
    document.Delete(elementId);
    transaction.Commit();

    //2. The delegate body executes with the provided argument
});

private void DeleteWindow(ElementId elementId)
{
    _deleteWindowEvent.Raise(elementId);

    //1. Raise returns immediately, the delegate is queued
}
AsyncExternalEvent

An asynchronous external event that queues the handler and asynchronously awaits its completion. The RaiseAsync() method returns a Task that completes when Revit has finished executing the delegate.

Exceptions thrown inside the delegate are rethrown in the original synchronization context.

Synchronously blocking the result of RaiseAsync() on the Revit main thread (e.g. .Wait(), .Result, .GetAwaiter().GetResult()) will cause a deadlock, because Revit cannot process the external event while its main thread is blocked by the waiting call. Use await instead, which releases the thread and allows Revit to process the event.

private readonly AsyncExternalEvent _deleteWindowsAsyncEvent = new(application =>
{
    var document = application.ActiveUIDocument.Document;
    using var transaction = new Transaction(document, "Delete windows");
    transaction.Start();
    document.Delete(document.GetInstanceIds(BuiltInCategory.OST_Windows));
    transaction.Commit();

    //1. The delegate body executes when Revit processes the event
});

private async Task DeleteWindowsAsync()
{
    await _deleteWindowsAsyncEvent.RaiseAsync();

    //2. Continues after the delegate has completed
}
AsyncExternalEvent<T>

A generic asynchronous external event that accepts an argument of type T and asynchronously awaits completion.

private readonly AsyncExternalEvent<ElementId> _deleteWindowAsyncEvent = new((application, elementId) =>
{
    var document = application.ActiveUIDocument.Document;
    using var transaction = new Transaction(document, "Delete window");
    transaction.Start();
    document.Delete(elementId);
    transaction.Commit();

    //1. The delegate body executes with the provided argument
});

private async Task DeleteWindowAsync(ElementId elementId)
{
    await _deleteWindowAsyncEvent.RaiseAsync(elementId);

    //2. Continues after the delegate has completed
}
AsyncRequestExternalEvent<TResult>

An asynchronous external event that returns a result of type TResult via RaiseAsync(). The handler is queued to Revit and the returned Task<TResult> completes with the result once Revit has finished executing the delegate.

private readonly AsyncRequestExternalEvent<int> _countWindowsAsyncEvent = new(application =>
{
    var document = application.ActiveUIDocument.Document;
    var elementIds = document.GetInstanceIds(BuiltInCategory.OST_Windows);

    //1. The delegate body executes and returns a value
    return elementIds.Count;
});

private async Task CountWindowsAsync()
{
    var count = await _countWindowsAsyncEvent.RaiseAsync();

    //2. Continues after the delegate has completed, with the result
}
AsyncRequestExternalEvent<T, TResult>

A generic asynchronous external event that accepts an argument of type T and returns a result of type TResult.

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();

    //1. The delegate body executes with the provided argument and returns a value
    return true;
});

private async Task DeleteWindowAsync(ElementId elementId)
{
    var result = await _deleteWindowRequestEvent.RaiseAsync(elementId);

    //2. Continues after the delegate has completed, with the result
}
ExternalEventOptions

You can configure the behavior of external events using ExternalEventOptions. The AllowDirectInvocation option enables the handler to be invoked directly on the calling thread when Revit is in API mode, instead of being queued:

private readonly ExternalEvent _deleteWindowsEvent = new(application =>
{
    //Execution logic
}, ExternalEventOptions.AllowDirectInvocation);

This option useful if you want support Modal and Modeless windows from a single codebase without wasting time on queue management and Revit event-processing cycle.

ExternalEvent attribute

The ExternalEventAttribute is an attribute that allows generating external event properties for annotated methods. Its purpose is to completely eliminate the boilerplate that is needed to define external events wrapping private methods in a class.

How it works

The ExternalEvent attribute can be used to annotate a method in a partial type, like so:

public partial class MyViewModel
{
    [ExternalEvent]
    private void DeleteWindows(UIApplication application)
    {
        var document = application.ActiveUIDocument.Document;
        using var transaction = new Transaction(document, "Delete windows");
        transaction.Start();
        document.Delete(document.GetInstanceIds(BuiltInCategory.OST_Windows));
        transaction.Commit();
    }
}

And it will generate properties like this:

public partial class MyViewModel
{
    public IExternalEvent DeleteWindowsEvent => field ??= new ExternalEvent(DeleteWindows);
    public IAsyncExternalEvent DeleteWindowsAsyncEvent => field ??= new AsyncExternalEvent(DeleteWindows);
}

After you can call a Raise method:

public partial class MyViewModel
{
    private void DeleteCommand()
    {
        DeleteWindowsEvent.Raise();
    }
}

The name of the generated properties is created based on the method name. The generator appends Event for the synchronous property and AsyncEvent for the asynchronous property.

Methods without parameters

For void methods, the generator creates both sync and async properties:

[ExternalEvent]
private void DeleteWindows() 
{
    _document.Delete(_windowIds);
}

// Generates:
// IExternalEvent DeleteWindowsEvent
// IAsyncExternalEvent DeleteWindowsAsyncEvent

For methods that return a value, only an async property is generated:

[ExternalEvent]
private int CountWindows() 
{
    return _document.GetInstanceIds(BuiltInCategory.OST_Windows).Count;
}

// Generates:
// IAsyncRequestExternalEvent<int> CountWindowsAsyncEvent

Methods with UIApplication parameter

When a method accepts a UIApplication parameter, the generated events pass the application instance to the handler:

[ExternalEvent]
private void DeleteWindows(UIApplication application)
{
    var document = application.ActiveUIDocument.Document;
    using var transaction = new Transaction(document, "Delete windows");
    transaction.Start();
    document.Delete(document.GetInstanceIds(BuiltInCategory.OST_Windows));
    transaction.Commit();
}

// Generates:
// IExternalEvent DeleteWindowsEvent
// IAsyncExternalEvent DeleteWindowsAsyncEvent

Methods with one extra parameter

When a method has one additional parameter beyond the optional UIApplication, the generator creates typed event properties:

[ExternalEvent]
private void DeleteWindow(UIApplication application, ElementId elementId)
{
    var document = application.ActiveUIDocument.Document;
    using var transaction = new Transaction(document, "Delete window");
    transaction.Start();
    document.Delete(elementId);
    transaction.Commit();
}

// Generates:
// IExternalEvent<ElementId> DeleteWindowEvent
// IAsyncExternalEvent<ElementId> DeleteWindowAsyncEvent

For methods with a return value:

[ExternalEvent]
private int CountWindows(UIApplication application, BuiltInCategory category)
{
    return application.ActiveUIDocument.Document.GetInstanceIds(category).Count;
}

// Generates:
// IAsyncRequestExternalEvent<BuiltInCategory, int> CountWindowsAsyncEvent

Methods with multiple extra parameters

When a method has two or more extra parameters, the generator creates a sealed record to bundle them into a single argument type, along with extension methods to call Raise with individual parameters:

public partial class MyViewModel
{
    private Room CreateRoom(UIApplication application, Level level, UV coordinate)
    {
        var document = application.ActiveUIDocument.Document;
        using var transaction = new Transaction(document, "Create room");
        transaction.Start();
        var createdRoom = document.Create.NewRoom(level, coordinate);
        transaction.Commit();

        return createdRoom;
    }
}

Will generate:

public partial class MyViewModel
{
    public IAsyncRequestExternalEvent<CreateRoomArgs, Room> CreateRoomAsyncEvent => field ??= new IAsyncRequestExternalEvent<CreateRoomArgs, Room>(...);

    public sealed record CreateRoomArgs(Level Level, UV Coordinate);
}

public static partial class MyViewModelExtensions
{
    public static Task<Room> RaiseAsync(this IAsyncRequestExternalEvent<CreateRoomArgs, Room> externalEvent, Level level, UV coordinate)
    {
        return externalEvent.RaiseAsync(new CreateRoomArgs(level, coordinate));
    }
}

These extensions allow you to call the event with individual arguments instead of creating a new Args:

var room = await CreateRoomAsyncEvent.RaiseAsync(level, coordinate);

Enabling direct invocation

Use the AllowDirectInvocation property on the attribute to configure the generated events to execute directly when Revit is in API mode:

[ExternalEvent(AllowDirectInvocation = true)]
private void DeleteWindows(UIApplication application)
{
    //Execution logic
}

Context

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.

RevitApiContext

Provides members for accessing the Revit application context at the database level.

List of available environment properties:

  • RevitApiContext.Application

RevitApiContext data can be accessed from any application execution location.

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();
}
RevitContext

Provides members for accessing the Revit application context at the UI level.

List of available environment properties:

  • RevitContext.UiApplication
  • RevitContext.ActiveDocument
  • RevitContext.ActiveUiDocument
  • RevitContext.ActiveView
  • RevitContext.ActiveGraphicalView
  • RevitContext.IsRevitInApiMode

RevitContext data can be accessed from any application execution location.

If your application can run in a separate thread or use API requests in an asynchronous context, use IsRevitInApiMode property to verify Revit API context:

public void Execute()
{
    if (RevitContext.IsRevitInApiMode)
    {
        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();
}

Options

The Toolkit provides implementation of various Revit interfaces, with the possibility of customization.

FamilyLoadOptions

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);
DuplicateTypeNamesHandler

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);
SaveSharedCoordinatesCallback

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;
}));
FrameworkElementCreator

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);
    });
SelectionConfiguration

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);

Decorators

Simplified implementation of raw Revit classes

DockablePaneProvider

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
        };
    });

Helpers

Provides auxiliary components

ResolveHelper

Provides handlers to resolve dependencies.

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.

Product Compatible and additional computed target framework versions.
.NET Framework net48 is compatible.  net481 was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETFramework 4.8

    • No dependencies.

NuGet packages (3)

Showing the top 3 NuGet packages that depend on Nice3point.Revit.Toolkit:

Package Downloads
HcBimUtils

Package Description

HC.Tech.So.Revit.Templates

Templates for Revit plugin development

DTDucas.RevitBase

Base framework for Revit API development with comprehensive command handling, exception suppression, and external event management

GitHub repositories (2)

Showing the top 2 popular GitHub repositories that depend on Nice3point.Revit.Toolkit:

Repository Stars
lookup-foundation/RevitLookup
Interactive Revit RFA and RVT project database exploration tool to view and navigate BIM element parameters, properties and relationships.
Nice3point/RevitTemplates
Templates for creating Revit add-ins
Version Downloads Last Updated
2027.0.0 143 4/7/2026
2027.0.0-preview.5.20260329 74 3/29/2026
2027.0.0-preview.4.20260310 87 3/10/2026
2026.1.0 309 4/7/2026
2026.1.0-preview.5.20260329 71 3/29/2026
2026.1.0-preview.4.20260310 98 3/10/2026
2025.2.0 353 4/7/2026
2025.2.0-preview.5.20260329 69 3/29/2026
2025.2.0-preview.4.20260310 95 3/10/2026
2024.3.0 342 4/7/2026
2024.3.0-preview.5.20260329 66 3/29/2026
2024.3.0-preview.4.20260310 79 3/10/2026
2023.4.0 315 4/7/2026
2023.4.0-preview.5.20260329 96 3/29/2026
2022.4.0 305 4/7/2026
2022.4.0-preview.5.20260329 59 3/29/2026
2021.4.0 184 4/7/2026
2021.4.0-preview.5.20260329 61 3/29/2026
2020.4.0 125 4/7/2026
2020.4.0-preview.5.20260329 47 3/29/2026
Loading failed