Announcements
Autodesk Community will be read-only between April 26 and April 27 as we complete essential maintenance. We will remove this banner once completed. Thanks for your understanding

How to know if Revit API is in Context.

ricaun
Advisor

How to know if Revit API is in Context.

ricaun
Advisor
Advisor

After messing with Revit API for so long I figured out a reliable way to know if your code is in Revit API Context or not.

 

By default, Revit API throws exceptions if your code is trying to execute Revit API methods in a WPF modeless view for example, that's the reason you need to use ExternalEvent to execute Revit API code in context.

 

Sometimes it is needed to know if the code is running in context or if not, to just execute the Revit API code right away or send it to ExternalEvent to be executed.

 

If you have access to UIApplication or UIControllerApplication, and if you try to subscribe to an event outside Revit API context you are gonna have this exception: Invalid call to Revit API! Revit is currently not within an API context.

 

Meaning you could use that to know if your code is in context or not.

 

ricaun_2-1708538347775.png

 

Here is a code sample and video: https://ricaun.com/revit-api-context/

ricaun_0-1708539573548.gif

 

I'm this technique using my open-source library to manage the creation of ExternalEvent if it is not in context and enable it to run Revit API asynchronously. https://github.com/ricaun-io/ricaun.Revit.UI.Tasks

 

ricaun_1-1708539573548.gif

 

I'm planning to create a full tutorial in the future about this library on my YouTube channel.

 

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

Reply
2,736 Views
17 Replies
Replies (17)

moturi.magati.george
Autodesk
Autodesk

Nice work

  Moturi George,     Developer Advocacy and Support,  ADN Open
0 Likes

jeremy_tammik
Autodesk
Autodesk

Shared here on the blog:

  

https://thebuildingcoder.typepad.com/blog/2024/03/api-context-aps-toolkit-and-da4r-debugging.html#2

  

Thank you!

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open

nice3point
Advocate
Advocate

The Preview version of RevitToolkit provides a way to do this in a more efficient way.

 

Throwing an exception takes quite a bit of time, and creating additional empty EventHandler<IdlingEventArgs> increases memory allocation.

 

Toolkit now has Context.IsRevitInApiMode property, that determines whether Revit is in API mode or not

 

Benchamarks:

 

IsRevitInApiMode
Exception median: 24122.4 ns
Native median: 413.1 ns


IsRevitOtsideApiMode
Exception median: 9522530.7 ns
Native median: 550 ns

 

The difference in speed is 58 times in context, and 17313 outside (1000 iterations)

 

2025.0.2-preview.1.0 is available for testing

https://github.com/Nice3point/RevitToolkit#context

ricaun
Advisor
Advisor

Neat!

 

A method inside the APIUIAPI that does the context check. I wonder what others useful stuff is in that assembly.

 

Thanks for sharing! 👏

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes

jeremy_tammik
Autodesk
Autodesk

Thank you for discovering and sharing this. Those are impressive results indeed. Shared on the blog as well:

  

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open

ricaun
Advisor
Advisor

Looks like the ActiveAddInId is linked with the Revit API Context.

 

I never realized it, but the ActiveAddInId is always null when outside the Revit API Context.

 

bool addInContext = uiapp.ActiveAddInId is not null;

 

It makes sense that your code would always need an AddIn Context to execute Revit code. Some Revit API methods only allows a specific AddIn to interact, like in the IUpdater and ExtensibleStorage.

 

I did some benchmark and looks similar like the IsRevitInApiMode implementation. @nice3point how did you create your benchamark, do you have some sample? I did a simple one using Stopwatch.

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes

nice3point
Advocate
Advocate

@ricaun 

Nice variant, however is it possible to guarantee that this property is 100% context related ? And additionally it requires a reference to the UiAppication.

I'm unlikely to find a benchmark, but yes, I've used StopWatch that way, and additionally 100 iterations on ColdStart ( it doesn't include iteration time in the result), because of the need for Jit compilation, which affects the first startup.

0 Likes

ricaun
Advisor
Advisor

@nice3point wrote:

Nice variant, however is it possible to guarantee that this property is 100% context related ?


@jeremy_tammikcould you confirm with the dev team that the ActiveAddInId is always available when Revit is in context.

 

I can't think in a example in Revit that is not true.

 

Only in DA4R that ActiveAddInId is null, but is a bug found recently: https://forums.autodesk.com/t5/revit-api-forum/revit-design-automation-extensible-storage-quot-writi...

 

 


@nice3point wrote:

And additionally it requires a reference to the UiAppication.


Is kinda easy to get the UIApplication especially if you want to run a Revit code, and ActiveAddInId is available in the UIControlledApplication, ControlledApplication and Application.

 

 


@nice3point wrote:

I'm unlikely to find a benchmark, but yes, I've used StopWatch that way, and additionally 100 iterations on ColdStart ( it doesn't include iteration time in the result), because of the need for Jit compilation, which affects the first startup.


Nice, I created this test project with all 3 implementations: https://github.com/ricaun-io/RevitTest.InContext.Tests

  • InAddInContext: Using ActiveAddInId
  • InApiMode: Using APIUIAPI
  • InContext:  Using Idling exception

The InAddInContext and InApiMode have a similar time, 0.001 ms to 0.002 ms, and InContext is around 25 to 40 slower.

 

I gonna try to find some cases to check if ActiveAddInId is available inside Revit events or something, but I'm pretty sure if ActiveAddInId could be use to check the Revit API Context.

 

 

 

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes

nice3point
Advocate
Advocate

@ricaun RevitTest.InContext.Tests is a private repository for now, could you open it?

 

Benchmarks, remember to use Release mode:

public class StartupCommand : ExternalCommand
{
    private const int ColdStartIterations = 200;
    private const int TargetIterations = 3000;
    private readonly Stopwatch _monitor = new();

    public override void Execute()
    {
        List<Func<bool>> targets =
        [
            IsRevitInApiMode,
            IsAddinActive,
            IsIdling
        ];

        Console.WriteLine();
        Console.WriteLine("Inside context");

        ExecuteTargets(targets);
        
        Console.WriteLine();
        Console.WriteLine("Outside context");
        
        Task.Run(() => ExecuteTargets(targets));
    }

    private void ExecuteTargets(List<Func<bool>> targets)
    {
        foreach (var target in targets)
        {
            RunBenchmark(ColdStartIterations, target);

            _monitor.Start();
            RunBenchmark(TargetIterations, target);
            _monitor.Stop();

            Console.WriteLine($"{target.Method.Name}: {_monitor.Elapsed.TotalNanoseconds / TargetIterations} ns.");
            _monitor.Reset();
        }
    }

    private static void RunBenchmark(int iterations, Func<bool> target)
    {
        for (var i = 0; i < iterations; i++)
        {
            try
            {
                target.Invoke();
            }
            catch
            {
                //ignored
            }
        }
    }

    private static bool IsRevitInApiMode()
    {
        return Context.IsRevitInApiMode;
    }

    private static bool IsAddinActive()
    {
        return Context.UiApplication.ActiveAddInId is not null;
    }

    private static bool IsIdling()
    {
        try
        {
            Context.UiApplication.Idling += OnApplicationIdling;
            Context.UiApplication.Idling -= OnApplicationIdling;
            return true;

            void OnApplicationIdling(object sender, Autodesk.Revit.UI.Events.IdlingEventArgs e)
            {
            }
        }
        catch
        {
            return false;
        }
    }
}

 

Inside context
IsRevitInApiMode: 277 ns.
IsAddinActive: 465 ns.
IsIdling: 15910 ns.

 

Outside context
IsRevitInApiMode: 303 ns.
IsAddinActive: 536 ns.
IsIdling: 20945 ns.

jeremy_tammik
Autodesk
Autodesk

Sure; I asked the development team.

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open

ricaun
Advisor
Advisor

My bad, I change to public. RevitTest.InContext.Tests 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes

ricaun
Advisor
Advisor

I add a test ToolkitTests similar like your StartupCommand but the results looks a little strange. IsRevitInApiMode looks slow.

 

Inside context
IsRevitInApiMode: 55981.6 ns.
IsAddinActive: 858.6 ns.
IsIdling: 27201.3 ns.

 

Outside context
IsRevitInApiMode: 52370.4 ns.
IsAddinActive: 756.3 ns.
IsIdling: 33753.9 ns.

 

Edited: Add link to the ToolkitTests.cs

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

ricaun
Advisor
Advisor

RevitLookUp uses a Toolkit version without IsRevitInApiMode, an exception was happening.

 

 

System.MissingMethodException: Method not found: 'Boolean Nice3point.Revit.Toolkit.Context.get_IsRevitInApiMode()'.

 

 

Now with the correct version looks better.

 

Inside context
IsRevitInApiMode: 394.2 ns.
IsAddinActive: 744.7 ns.
IsIdling: 21207.7 ns.

 

Outside context
IsRevitInApiMode: 379.7 ns.
IsAddinActive: 744.0 ns.
IsIdling: 36853.7 ns.

 

 

 

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes

nice3point
Advocate
Advocate

@ricaun wrote:

RevitLookUp uses a Toolkit version without IsRevitInApiMode, an exception was happening.


Yes, that's the first thing I thought, Lookup is now using a previous version of Toolkit, you can run the dependency update tool https://github.com/jeremytammik/RevitLookup/issues/269 but I see everything is already in order.

 

IsRevitInApiMode looks a bit faster because it doesn't contain additional calls to auxiliary functions like ActiveAddin. And if the development team confirms your theory, ActiveAddin may well be used for developers who don't use Toolkit

0 Likes

ricaun
Advisor
Advisor

Yes, I just updated manually Nice3point.Revit.Toolkit.dll version in the RevitLookup folder.

 

The only think I don't like about the IsRevitInApiMode is that uses the APIUIAPI.dll, if was RevitAPI.dll or RevitAPIUI.dll would be perfect.

 

I hope I'm correct, and is kinda simpler to explain that you need an AddIn Context to run a Revit code, with ActiveAddInId null is kinda obvious that Revit could reject the code without valid AddIn Context.

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes

nice3point
Advocate
Advocate

The same thing could be called in RevitAPIUI, but will require more coding, APIUIAPI calls the method directly, with less latency, so this option is more performant and easier to understand for code maintenance

ricaun
Advisor
Advisor

Revit AddIn Context is what I'm calling the Revit API Context now.

 

I tested in a lot of places inside the Revit API and the ActiveAddInId is always valid because Revit tracks what AddInId is executing the code.


Some methods requires to have an AddInId like the Extensible Storage, and when registering a IExternalCommand in a panel the command always generate the same AddInId that was used to register the command.

 

This is the basic code to check if Revit is in the AddIn Context: https://ricaun.com/revit-addin-context/

bool InAddInContext(UIApplication application)
{
    // ActiveAddInId is null when invoke outside Revit Api context.
    return application.ActiveAddInId is not null;
}

 

 

There is only one place that the ActiveAddInId is null and Revit API in in Context, inside the Design Automation for Revit event. That's is a bug/limitation and what made me looking in to the ActiveAddInId propriety closer.

And because now I know how the ActiveAddInId works, I can use ExternalService inside Design Automation for Revit to make the event to run in a valid ActiveAddInId.

 

So I created a library for Design Automation for Revit to fix that and another issue I'm having inside DA4R.

 

Just need to create a video on YouTube about the package.

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils