flowchart LR
subgraph Load["Load Phase"]
UI["UI/ViewModel"] --> Orch["ExecutionOrchestrator"]
Orch --> Provider["IExecutionProvider"]
Provider --> Tree["ExecutionNodeRoot"]
end
subgraph Execute["Execute Phase"]
Tree --> Strategy["IExecutionStrategy\n(Python/IronPy/FSharp/CSharp/Assembly)"]
Strategy --> Host["Host Adapter\n(IHostContextExecutor)"]
Host --> Result["ExecutionResult"]
end
subgraph Watch["File Watch"]
Watcher["FileWatcherService"] -.->|file changed| Orch
end
Load: UI requests a path → orchestrator asks providers to discover nodes → builds execution tree.
Execute: User selects a node → strategy compiles/interprets → dispatches through IHostContextExecutor → returns result. AutoCAD dispatch is autocad-context.md.
Watch: File watcher monitors roots → triggers reload on changes → TreeStateManager preserves UI state.
| Mode | Provider | Notes |
|---|---|---|
Script |
ScriptExecutionProvider |
Folder scan for *script.py, *script.fsx, *script.csx. |
Assembly |
AssemblyExecutionProvider |
.dll reflection. Host-specific ICommandDiscovery. |
ScriptExecutionProvider skips folders: docs, resources, bin, obj, packages, node_modules, output, caches, virtualenvs, and agent/tool folders.
Init, backends, host-attach, and native constraints: python-runtime.md.
- Host owns CPython → attach pythonnet, then uv. Pixi is not tried.
- No host interpreter → Pixi. uv is not tried.
- Pip only if that chosen manager’s setup/
VerifyRunnableAsyncfails. - Overlay,
python3.dllforwarder, and init order: python-runtime.md.
IronPythonExecutionStrategy executes *_ipy_script.py. Policy: 0033.
Stacks. pyRevit loaded → IIronPythonBridge.TryGetHostEngine (ScriptExecutor) and static IronPythonDebugger on that loader engine (clean: false, full_frame: false, reuse). No pyRevit → embedded 3.4.2: IronPythonInitializer creates the session engine in its InitializeAsync sync prefix; IronPythonExecutor runs scripts on that engine. Same static debugger. Do not CreateEngine or Runtime.Shutdown on the loader engine.
Bridges. Host bridges configure builtins and CLR assemblies only. AutoCAD AcadIronPythonBridge loads AcCoreMgd / AcDbMgd / AcMgd (parity with CPython SetupAcad.py). Revit injects __revit__ and RevitAPI / RevitAPIUI. DlrScriptHost is the DLR façade (typed 3.4 or ReflectionBound on a foreign loader engine). pyRevit ScriptExecutor / PyRevitLoader bindings stay in the Revit host.
Search paths. Embedded 3.4.2 uses IronPythonSearchPaths.ForNativeHost only. On the pyRevit engine, SetSearchPaths replaces the list for that command. Final order:
- pydevd extract root
- script directory
- hierarchy
lib/bin - TFM-selected extension DLL directories
- loaded folder
- search paths a loaded pyRevit
ScriptCommandalready carries pyrevitlib, thensite-packages
pyrevitlib / site-packages come from walking PyRevitLoader up to pyRevitfile. The ScriptCommand string is written by pyRevit 5, 6, and the pyRevit 7 Python loader from extensionmgr (*.lib root only). The pyRevit 7 C# loader writes it from LibraryExtensionSearchPaths.Collect (*.lib root plus nested lib/ when present). When no command assembly is loaded yet, the pyRevit 7 builder methods are the fallback. PyRevitAssemblyLoader picks one managed DLL per simple name from lib/** and bin/*/ by TargetFrameworkAttribute vs the host runtime. One load per *.extension.
Each run. The same engine stays up so the pydevd listener survives. IronPythonDebugger.DropUnknownModules drops sys.modules entries whose __file__ is a real file outside two known roots: the pyRevit install (directory that contains pyRevitfile, so pyrevitlib and site-packages) and the pydevd extract. Modules with no __file__, and embedded stdlib whose __file__ is <resource>, stay.
Debugger. Embedded 3.4.2 uses Frames without Tracing. The pyRevit loader engine must not set full_frame: that enables Tracing, and IronPython 3.4 then cannot import pydevd. from pyrevit import HOST_APP runs on the loader engine before pydevd enable_tracing (warmup and each Run) so _perf.mark() cannot import coreutils while HOST_APP is still unset. Listen sets PYDEVD_DISABLE_FILE_VALIDATION before importing pydevd under cli, then forces IS_WINDOWS=True. pyRevit's embedded stdlib records co_filename as ntpath.py; without the flag, pydevd prints that file-validation warning to stderr at startup.
In-process debugger is PyDev.Debugger 2.8.0 under %APPDATA%\RevitDevTool\pydevd\PyDev.Debugger-pydev_debugger_2_8_0. The host listens with HTTP_JSON_PROTOCOL + pydevd._enable_attach on 4567 (no _wait_for_attach). VS Code/Cursor attaches with "type": "debugpy" connect localhost:4567 (same client as CPython on 5678; two sockets). Handshake and expand-getattr quieting live in IpyDebugger.py. Execution UI chips read DebugEndpoints (Port, Attached per runtime).
FSharpExecutionStrategycompiles.fsxthroughFSharpCompilationCache.FSharpDependencyResolverhandles#r "nuget: ..."directives.NugetManagerrestores packages under%APPDATA%\RevitDevTool\nuget.- When nuget or file
#rmust be rewritten, the graph is copied under%TEMP%\DevTools\fsx_cache(commented#r, remapped#load,--reference:). Each temp file starts with#line 1 "<original>"so FSI--debug+sequence points map back to the source the user edits. Eval of an unchanged graph still uses the original path. - Compilation has a hard timeout.
- Host year
#ifsymbols (REVIT/AUTOCAD,{HOST}{year},{HOST}{year}_OR_GREATER) come fromCompileScriptSymbolsviaIHostAppInfo.VersionNumber, matchingprops/Revit.targetsandprops/AutoCad.targets. FSI gets them as--define:.
CSharpExecutionStrategycompiles.csxthroughCSharpCompilationCache.CSharpDirectiveParserhandles references and package directives.#r/#loadare commented in place (line numbers stay aligned with the file on disk).- AppDomain assemblies are still imported as Roslyn metadata refs (
#r/ NuGet first). Duplicate simple names (e.g. Revit 2027Autodesk.Http.*underAddIns\IssuesManagement) are skipped so CS1704 does not fail the compile. - Emit is Debug + portable PDB, loaded with the collectible/net48 isolation session so an attached host debugger can bind the original
.csx.#r nugetstill resolves throughNugetManager; it is not left as compiler syntax. - Compiled script outputs use the feature-owned
ScriptIsolationPlanwith the shared assembly-isolation session. Identity and lifecycle behavior follows the assembly-isolation product contract. - Compilation has a hard timeout.
- Roslyn parse options take the same
CompileScriptSymbolslist, so.csxcan use#if REVIT2025_OR_GREATER(and AutoCAD-family equivalents) like host add-in code.
AssemblyExecutionStrategyloads IL from.dll, invokes method.- Also used as MCP source kind for .NET assembly tools.
PackageService is the UI facade. It branches on marketplace only:
| Marketplace | Store |
|---|---|
| NuGet | NugetPackageStore |
| CondaForge / PyPI | IPythonPackageStore for the current PythonBackend |
Python backends are equal implementations of IPythonPackageStore (PixiPackageStore, UvPackageStore, PipPackageStore). PackageService picks the store whose Backend matches PythonInitializer.Provider. No switch on backend inside PackageService.
- uv: host-owned-interpreter sidecar (PyPI, version-matched).
- Pixi: owns in-process CPython when the host has no interpreter (conda-forge + PyPI).
- pip: last chain step — pyRevit
cengineswhen the chosen Pixi or uv manager cannot run.
Operations: list, remove, remove all, update latest, and repair.