Potential Rocket Mode Bug: IExternalEventHandler loses locals/scope/imports

The purpose of this post is so I can decide if I should create an issue for this on Github. The problem is that I’m not sure what is the expected behavior between Rocket Mode and IExternalEventHandler, so I don’t know if it is indeed an issue with pyRevit, or with my code.

When I use a modeless form that triggers external events, the event handler loses references to all functions and imports, thus a NameError is thrown for any function or import called. This only happens while Rocket Mode is enabled in pyRevit 6.5.3. The same exact code was working with previous versions of pyRevit, so I’m thinking that this is a bug.

Running the script with a clean engine fixes the NameErrors, but obviously, this defeats the purpose of Rocket Mode.

To repeat the question: Is it expected that Rocket Mode will cause an IExternalEventHandler to lose all defined names/scope/functions/variables?

Does anyone know off the top of their head?

P.S. I did not share any of my code because it would not illustrate anything.

Enable Rocket Mode > Use IExternalEventHandler > Calling any function or import from the event handler’s Execute() method triggers NameError.

That’s all there is to it. The code works without Rocket Mode, and it used to work with previous versions of pyRevit.

@romangolev could you take a look?

Here is a minimum reproducible example. Throw these files in a pushbutton and you should get inexplicable name errors:

script.py:

# Dependencies
import clr
clr.AddReference('System.Windows.Forms')
clr.AddReference('PresentationCore')

# Import from pyrevit
from pyrevit       import script
from pyrevit.forms import WPFWindow
from pyrevit.revit import Transaction as pyrevit_transaction, TransactionGroup
xamlfile = script.get_bundle_file('ui.xaml') # find the path of ui.xaml

# Import WPF creator and base Window
from System import EventHandler as WinEventHandler

# Import python system module
import sys
from collections import namedtuple

# Import Revit API objects
from Autodesk.Revit.DB import (ElementId, FilteredElementCollector, BuiltInCategory,
                               BuiltInParameter, Element, RevitLinkInstance, ViewPlan, UV, LinkElementId,
                               ParameterFilterRuleFactory, ElementParameterFilter, ElementCategoryFilter,
                               RevitLinkType, XYZ, ElementTransformUtils,
                               GeometryCreationUtilities, SolidUtils, BooleanOperationsUtils,
                               BooleanOperationsType, Options, PlanViewPlane,
                               ViewType, SubTransaction)

from Autodesk.Revit.DB.Architecture import RoomTag, Room
from Autodesk.Revit.UI              import IExternalEventHandler, ExternalEvent
from Autodesk.Revit.UI.Events       import DialogBoxShowingEventArgs, TaskDialogShowingEventArgs, ViewActivatedEventArgs

# Import .NET List
from System.Collections.Generic     import List

# Setup doc
uiapp = __revit__
uidoc = __revit__.ActiveUIDocument
doc   = __revit__.ActiveUIDocument.Document

# Classes
class EventHandler(IExternalEventHandler):
	def __init__(self, func_to_run):
		self.func = func_to_run
	
	def Execute(self, uiapp):
		try:
			self.func()
		except Exception as ex:
			import traceback
			print traceback.format_exc() # Prints the stack traceback

	def GetName(self):
		return "simple function executed by an IExternalEventHandler in a Form"

# WPF form used to call the ExternalEvents
class ModelessForm(WPFWindow):
	def __init__(self, xaml_file_name):
		WPFWindow.__init__(self, xaml_file_name)
		
		self.recenter_all_tags_in_view_handler = EventHandler(self.recenter_all_tags_in_view)
		self.recenter_all_tags_in_view_event_instance = ExternalEvent.Create(self.recenter_all_tags_in_view_handler)
		
		# Show modeless window
		self.show_self()
	
	
	# Properties
	@property
	def active_view(self):
		return uidoc.ActiveView
	
	@property
	def room_tags_in_model(self):
		result = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_RoomTags).WhereElementIsNotElementType()
		if result: return result
		else: pass
	
	@property
	def room_tags_in_view(self):
		result = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_RoomTags).WhereElementIsNotElementType().OwnedByView(self.active_view.Id)
		return result
	
	@property
	def misplaced_room_tags_in_model(self):
		result = [room_tag for room_tag in self.room_tags_in_model if not room_tag.IsInRoom]
		return result
	
	@property
	def misplaced_in_view(self):
		result = [room_tag for room_tag in self.room_tags_in_view if not room_tag.IsInRoom]
		return result
	
	# Methods
	def show_self(self):
		self.Show()
	
	# Methods to call via external event
	def recenter_all_tags_in_view(self):
		misplaced_in_view = self.misplaced_in_view
		print(misplaced_in_view)

	
	# Events to call from xaml UI
	def unsubscribe_event(self, sender, args):
		self.unsubscribe_event_instance.Raise()

	def recenter_all_tags_in_view_event(self, sender, args):
		self.recenter_all_tags_in_view_event_instance.Raise()

# Instantiate the form (entry point)
if __name__ == '__main__':
	modeless_form = ModelessForm("ui.xaml")

ui.xaml:

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
		xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
		xmlns:av="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:Collections="clr-namespace:System.Collections;assembly=mscorlib" mc:Ignorable="av"
		Title="Fix Room Tags" ResizeMode="NoResize" SizeToContent="WidthAndHeight" Background="WhiteSmoke" BorderBrush="#FF89AF98" Topmost="True"
        Closing="unsubscribe_event">
	<StackPanel x:Name="main_stackpanel" Margin="10">
		<StackPanel Orientation="Horizontal" Margin="0,10,0,0">
			<StackPanel Orientation="Vertical" Margin="0,0,10,0">
                <Button Content="Re-center all misplaced tags in view" Click="recenter_all_tags_in_view_event" HorizontalContentAlignment="Right" Padding="1,1,5,1" />
            </StackPanel>
		</StackPanel>
	</StackPanel>
</Window>

And errors log please
Reading quickly through the code,
Not sure why the CLR imports

Did you set the correct engine persistent flag in your bundle file? I can’t reproduce this with your sample.

engine:
  clean: false # imported modules will stay imported to speed up scripts
  full_frame: false # all frame information, usually not needed, higher memory footprint 
  persistent: true # keeps global frame in memory

Thanks for looking into it.

The CLR imports are for importing Windows forms & WPF for the UI that I am using.

@pyrevti Thanks for the suggestion. I tried adding that to the bundle.yaml file. No change.

The errors are only thrown when Rocket mode setting is enabled and extension.json contains "rocket_mode_compatible": "True". When attempting to reproduce the bug, please make sure both of those things are true. Disable either of those and the errors cease.

FilteredElementCollector (and all other imports) throw a NameError every time they are called from the UI-triggered external event, even though they are imported at the top of the script. I even tried lazy importing during the Execute() method, but NameError still gets thrown.

Here is the debug log when first activating the pushbutton:

DEBUG [pyrevit.perf] [PERF:py] pyrevit.__init__:before labs: 13ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.labs:entry: 311ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.labs:after clr.AddReference block (14 pyRevitLabs DLLs): 2ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.labs:after `from pyRevitLabs import` block: 17ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.labs:exit: 1ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.__init__:after labs (exit): 0ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.revit:entry: 61ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.revit:after `from pyrevit.revit.db import *`: 8ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.revit:after db.query/select/create/update/ensure/delete: 46ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.revit:after db.transaction *: 0ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.revit:after db.failure: 0ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.revit:after db.pickling *: 2ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.revit:after journals *: 19ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.revit:after selection *: 3ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.runtime:entry: 7ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.userconfig:entry: 14ms
DEBUG [pyrevit.coreutils.git] Loading dll: C:/Users/***/AppData/Roaming/pyRevit-Master/bin/netfx/LibGit2Sharp.dll
DEBUG [pyrevit.perf] [PERF:py] pyrevit.userconfig:after imports: 11ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.userconfig:find_config_file x3 (HOME / ALLUSER / USER): 1ms
DEBUG [pyrevit.userconfig] Using User config file: C:/Users/***/AppData/Roaming/pyRevit/pyRevit_config.ini
DEBUG [pyrevit.userconfig] Debug mode is enabled in user settings.
DEBUG [pyrevit.perf] [PERF:py] pyrevit.userconfig:verify_configs(CONFIG_FILE): 111ms
DEBUG [pyrevit.userconfig] Debug mode is enabled in user settings.
DEBUG [pyrevit.perf] [PERF:py] pyrevit.userconfig:PyRevitConfig(__init__): 27ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.userconfig:upgrade.upgrade_user_config: 214ms
DEBUG [pyrevit.userconfig] Debug mode is enabled in user settings.
DEBUG [pyrevit.perf] [PERF:py] pyrevit.userconfig:user_config.save_changes: 19ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.userconfig:exit (user_config ready): 385ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.runtime:after imports: 2ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.runtime:after dotnet dir listings: 1ms
DEBUG [pyrevit.perf] [PERF:py] userconfig.get_active_cpython_engine:GetAttached: 0ms
DEBUG [pyrevit.perf] [PERF:py] userconfig.get_active_cpython_engine:clone.GetCPythonEngines: 1ms
DEBUG [pyrevit.userconfig] cpython engines dict: {<pyRevitLabs.PyRevit.PyRevitEngineVersion object at 0x00000000000005A1 [3123]>: <pyRevitLabs.PyRevit.PyRevitEngine object at 0x00000000000005A2 [CPY3123 (netfx) | Kernel: CPython | Version: 3123 | Runtime: False | Path: "C:/Users/***/AppData/Roaming/pyRevit-Master/bin/cengines/CPY3123/python312.dll" | Desc: "CPython Engine"]>}
DEBUG [pyrevit.perf] [PERF:py] pyrevit.runtime:after user_config.get_active_cpython_engine(): 12ms
DEBUG [pyrevit.runtime] Building on IronPython engine: 2712
DEBUG [pyrevit.perf] [PERF:py] pyrevit.runtime:after calculate_dir_hash + BASE_TYPES_DIR_HASH: 19ms
DEBUG [pyrevit.runtime] Interface types assembly file is: pyRevitLabs.PyRevit.Runtime.2023
DEBUG [pyrevit.perf] [PERF:py] pyrevit.runtime:after assmutils.find_loaded_asm: 25ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.runtime:exit (after 4x find_type_by_name): 1ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.revit:after ui: 5ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.revit:after tabs: 1ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.revit:after events: 8ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.revit:after report: 12ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.revit:after files: 1ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.revit:after serverutils: 2ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.revit:after geom: 0ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.revit:after units: 1ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.revit:after features: 0ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.revit:after bim360/dc3dserver/tmpgfx (exit): 15ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.revit:after bim360/dc3dserver/avf (exit): 66ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.forms._ipy:entry: 172ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.forms._ipy:after coreutils+logger+colors: 33ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.forms._ipy:after framework re-imports: 0ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.forms._ipy:after pyrevit.api.AdWindows: 0ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.forms._ipy:after `from pyrevit import revit, UI, DB`: 0ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.forms._ipy:after forms.utils + forms.toaster: 3ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.forms._ipy:after pyrevit.versionmgr: 0ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.forms._ipy:after `from pyrevit.userconfig import user_config`: 0ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.forms._ipy:after pyevent: 2ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.forms._ipy:after Autodesk.Windows/Internal: 0ms
DEBUG [pyrevit.perf] [PERF:py] pyrevit.forms._ipy:exit (all class defs done): 12ms

Here is the error traceback, which does not occur on activating the pushbutton, but only when clicking the button in the dialog that pops up:


Traceback (most recent call last):
File "C:\***.tab\Advanced.panel\Advanced Functions.pulldown\NameErrorMRE.pushbutton\script.py", line 47, in Execute
self.func()
File "C:\***.tab\Advanced.panel\Advanced Functions.pulldown\NameErrorMRE.pushbutton\script.py", line 99, in recenter_all_tags_in_view
misplaced_in_view = self.misplaced_in_view
File "C:\***.tab\Advanced.panel\Advanced Functions.pulldown\NameErrorMRE.pushbutton\script.py", line 90, in misplaced_in_view
result = [room_tag for room_tag in self.room_tags_in_view if not room_tag.IsInRoom]
File "C:\***.tab\Advanced.panel\Advanced Functions.pulldown\NameErrorMRE.pushbutton\script.py", line 80, in room_tags_in_view
result = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_RoomTags).WhereElementIsNotElementType().OwnedByView(self.active_view.Id)
NameError: name 'FilteredElementCollector' is not defined

AI suggested the following:

If this is really an ExternalEvent or modeless UI callback

If your “External Command” is actually part of a modeless WPF window, dockable pane, ExternalEvent, IExternalEventHandler, or a Revit callback registered from pyRevit, then the issue is even more likely: Revit calls Execute() later, outside the original pyRevit script execution context.

For those cases:

  1. Do not rely on globals from script.py.

  2. Put handler logic in a lib module.

  3. Import inside Execute().

  4. Do not store doc, uidoc, view, Selection, Element, or Transaction objects globally.

  5. Use element ids, stable config data, or simple DTO/state objects instead.

I’m not sure the above is really the issue, though, since it used to work in previous releases of pyRevit. That is the main reason I think it is a pyRevit bug.

Thank you all for your assistance.

I just installed pyRevit 5.3.1 and confirmed that the NameErrors do not occur in that older version.

Please open an issue in the repo

Tried two different machines, with 6.5.3 and latest WIP on Revit 2024. Can’t reproduce with your code sample.

Also FYI:
pyrevitlib/pyrevit/revit/events.py
contains a helper, so you don’t have to setup ExternalEventHandlers:


def execute_in_revit_context(func, *args, **kwargs):
    """
    Execute a function in Revit API context using ExternalEvent.

    Use this helper when calling Revit API from modeless dialogs,
    background threads, or any non-Revit context where direct API
    access would raise InvalidOperationException.

    The function executes asynchronously - it returns immediately
    and the function runs when Revit is idle.

    Args:
        func: Function to execute in Revit context
        *args: Positional arguments to pass to the function
        **kwargs: Keyword arguments to pass to the function

    Example:
        ```python
        # Simple function call
        execute_in_revit_context(transaction_function, doc, element_id)

        # From modeless dialog button click
        def on_button_click(sender, args):
            execute_in_revit_context(
                modify_elements,
                selected_ids,
                parameter_name="Comments",
                value="Updated"
            )
        ```

    Note:
        This function does not return values from the executed function.
        For return values, use callbacks or shared mutable objects.
    """