WillTools 2026-09-09 -- clean snapshot of the development repo for review
ci / test (push) Successful in 6m18s

History-free snapshot of the private development repository at cbe45c5
(2026-09-09). Code, docs and public catalogue only; no plant or job
knowledge layers, no client or job names (tools/scrub.py clean). The
intro deck is left out of this snapshot because its slide images could
not be checked for job drawings.
This commit is contained in:
2026-09-09 08:32:15 -05:00
commit 8e6ee31596
406 changed files with 223435 additions and 0 deletions
@@ -0,0 +1,8 @@
title: Hot Reload
# No tooltip and no author on purpose -- the owner wants nothing on hover,
# and test_button_tooltips.py enforces it. Document the button in comments
# in script.py instead.
#
# One line: this is a row in a `.pulldown` (text beside a 16px icon), so a
# `\n` has nowhere to go. The dropdown grows to fit, so the title may read
# in full.
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

@@ -0,0 +1,93 @@
# -*- 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()