# -*- coding: utf-8 -*- # # Hot Reload -- stop the WillTools routes bridge cleanly, THEN reload pyRevit. # # Why the stop must come first: the routes HTTP dispatch is wired to a module- # global external event handler created once, on the host main thread, when # `pyrevit/routes/server/server.py` is first imported (server.py:35-36), and it # is NOT recreated across a reload -- pyRevit re-imports extension scripts, not # its own lib. A request in flight when the engine is torn down runs its handler # against a dead engine -> unhandled exception on a non-main thread -> Revit dies # uncatchably (measured 2026-08-14; see startup.py and CLAUDE.md "Never reload"). # Stopping the HTTP server first closes that crash window. On reload, startup.py # re-registers the routes and a fresh server comes back up automatically. # # If the server will NOT stop cleanly, this ABORTS rather than reloads -- a # reload with a live bridge is the exact crash, so a bad stop must never fall # through to it. # # IronPython 2.7: no f-strings. No module docstring / tooltip / author # (test_button_tooltips.py). # pylint: disable=import-error,invalid-name,broad-except from pyrevit import EXEC_PARAMS from pyrevit import routes from pyrevit import forms from pyrevit import script from pyrevit.loader import sessionmgr from pyrevit.loader import sessioninfo logger = script.get_logger() go = True if EXEC_PARAMS.executed_from_ui: go = forms.alert( 'Hot Reload stops the WillTools bridge, then reloads pyRevit.\n\n' 'Run this only when no bridge request is in flight -- no sweep, no ' 'agent mid-exec. A request raised the instant before this click cannot ' 'be cancelled and can still take Revit down.\n\nReload now?', ok=False, yes=True, no=True) if go: stopped_clean = True try: routes.deactivate_server() except Exception as ex: stopped_clean = False logger.error('deactivate_server raised: {0}'.format(ex)) # Verify the socket is actually gone before tearing the engine down. A # deactivate that "succeeded" but left the server up is still unsafe. try: if routes.get_active_server() is not None: stopped_clean = False except Exception as ex: stopped_clean = False logger.error('get_active_server raised: {0}'.format(ex)) if not stopped_clean: forms.alert( 'Could not stop the WillTools bridge cleanly.\n\n' 'Do NOT reload with a live bridge -- restart Revit instead of ' 'reloading. (A reload while the routes handler is live is what ' 'crashes Revit.)', ok=True) else: # Vouch to tools/rvt.py that THIS reload is a clean stop-first one, so # the reloaded bridge is not refused as a raw-reload landmine. startup.py # consumes this marker on the very next load and stamps that session # 'hotreload'. Keyed by pid: a reload is in-process, so the same Revit # picks it up and no other session can. Best-effort -- if it cannot be # written the only cost is rvt.py refusing the reloaded bridge (the safe # default), never a failed reload. try: import os as _os from System.Diagnostics import Process as _Process _here = _os.path.abspath(__file__).replace('\\', '/') _mark = _here.find('/WillTools.extension/') if _mark != -1: _root = _here[:_mark] else: _root = _os.path.join(_os.path.expanduser('~'), 'pyrevit-dev') _pid = _Process.GetCurrentProcess().Id _flagpath = _os.path.join( _root, '.bridge-hotreload-{0}.flag'.format(_pid)) _fh = open(_flagpath, 'w') _fh.write('hotreload\n') _fh.close() except Exception as ex: logger.warning('could not write hot-reload marker: {0}'.format(ex)) logger.info('WillTools bridge stopped. Reloading pyRevit...') sessionmgr.reload_pyrevit() # Mirrors the shipped Reload button: record the new session id. script.get_results().newsession = sessioninfo.get_session_uuid()