Skip to content

Understanding pyRevit Architecture

This guide provides an overview of pyRevit’s architecture to help new contributors understand how the software works.

Whether you want to create tools, troubleshoot issues, or contribute code, understanding these components will help you navigate the project.

Components of pyRevit

  1. pyRevit Add-In (pyRevitLoader) — dev/pyRevitLoader/

    • A small piece of C# code that starts pyRevit inside Revit.
    • Calls the C# session manager directly to build the UI and button commands (no IronPython bootstrap).
  2. pyRevit Python Libraries (pyrevitlib) — pyrevitlib/pyrevit/

    • Python packages that simplify working with the .NET Revit API.
    • Provide tools to create ribbon buttons, run scripts, and more.
  3. Extensions — see Extensions for bundle structure, bundle.yaml, script engines, and hooks.

    • These are the tools and features users see inside Revit.
    • They are mostly written in python, but can also be C#/VB.NET scripts, dynamo projects, and so on
    • Bundled extensions appear in the "pyRevit" tab, offering many tools.
    • Users can add extensions by:
      • Enabling listed extensions in extensions/extensions.json via the "Extensions" button in pyRevit.
      • Creating custom extensions and adding their paths to the configuration.
  4. pyRevit Command-Line Interface (CLI) — dev/pyRevitLabs/pyRevitCLI/

    • A tool for managing configurations, running scripts in bulk, and troubleshooting.
    • Useful for corporate setups and advanced users.
    • The pyrevit env report includes the configured CPython engine version (activeCpythonEngineVersion in JSON).
  5. Telemetry Server

    • A small server (written in Go) that tracks usage data of pyRevit tools.
    • Stores data in MongoDB or PostgreSQL for business intelligence.
  6. Agent runtime (experimental) — dev/pyRevitLabs.PyRevit.Runtime/Agent/, pyrevitlib/pyrevit/agent/, pyrevit mcp

    • Lets MCP-capable coding agents run Python in a live Revit session through a local named pipe, with every model change approved in Revit.
    • See Agent runtime.

Command execution itself lives in a separate component, PyRevit.Runtime (dev/pyRevitLabs.PyRevit.Runtime/) — see How pyRevit Commands run.

How pyRevit Loads in Revit

TL;DR:

  • Revit reads the .addin manifest in the Addins folders
  • The .addin manifest points to pyRevitLoader.dll
  • pyRevitLoader.dll calls the C# session manager to build the UI and the button commands
  • The C# session manager runs session_preload.py / session_postload.py to drive the Python services that have not yet been ported to C#

.addin Manifest

  • The installer creates a file with .addin extension, called manifest, in the Revit Addins folder, instructing Revit to load pyRevit when it starts.
  • The Addins folder can be located in one of these paths, depending on pyRevit installation:
    • C:\ProgramData\Autodesk\Revit\Addins (for all users)
    • %APPDATA%\Autodesk\Revit\Addins (for the current user only)
  • The manifest points to pyRevitLoader.dll, which acts as the entry point for pyRevit.

pyRevitLoader.dll

The pyRevitLoader.dll file is a small C# program that:

  • Ensures required .NET assemblies are loaded.
  • Calls the C# session manager to load the session.
Info

the source code is in PyRevitLoaderApplication.cs and it is an implementation of the Revit API IExternalApplication interface (the standard way to create a plugin for Revit).

There are multiple versions of pyRevitLoader.dll to support:

  • different Revit versions:
    • One for Revit 2025 and newer, built with .NET 8.
    • Another for Revit 2021-2024, built with the .NET Framework.
  • different IronPython versions; to this date:
    • version 2.7.12, the default one
    • version 3.4.2, more recent but not fully tested.

They share the same source code, but are compiled against the different .net runtimes and IronPython versions.

The legacy pure-Python loader (which supported pre-2021 Revit versions) has been removed; the C# loader requires Revit 2021+. That floor is also why release/pyrevit-hosts.json carries no pre-2021 record — see Which Revit versions are listed.

Note

Only one IronPython runtime engine can be attached at a time; switching it (Settings UI or pyrevit attach) rewrites the .addin manifest to the matching pyRevitLoader.dll build. CPython selection is separate and doesn't touch the manifest. Manifests can also go stale from clone switches or mixed attachments — pyrevit attach/switch fixes them by recreating the manifest.

Session loading

PyRevitLoaderApplication.OnStartup calls SessionManagerService.LoadSession, which:

  • Runs session_preload.py to set up environment variables, the logging/script console, update checks, telemetry, and API routes.
  • Loads extensions and creates UI elements like ribbons and buttons in C# (see below).
  • Registers and activates hooks, which enable features like event-driven scripts.
  • Runs session_postload.py to finalize the session (cleanup, doc colorizer, routes server, output teardown).

A reload re-enters the same LoadSession entry, so initial startup and reload share one code path.

Info

session_preload.py and session_postload.py are small scripts that call pyrevit.loader.sessionmgr.perform_preload and pyrevit.loader.sessionmgr.perform_postload. They drive the residual Python session services that have not yet been ported to C#.

Extensions discovery

  • pyRevit scans known paths and user defined folders to find extensions.
  • For each extension, it builds a .net assembly to create buttons, tabs, and other UI elements.
Info

Extensions directories are detected by pyrevit.userconfig.PyRevitConfig.get_ext_root_dirs. Extension components discovery is performed by pyrevit.extensions.extensionmgr.get_installed_ui_extensions. Assemblies are generated by [pyrevit.loader.asmmaker][] with types from pyrevit.runtime.create_type.

How pyRevit Commands run

TL;DR:

Command execution is handled by the c# project pyRevitLabs.Pyrevit.Runtime

Each button generated by the Extension discovery is bound to a command derived by the ScriptCommand.cs source code.

This code deals with:

  • detecting the modifier keys hold while clicking the button, and change the behavior accordingly
  • calling ScriptExcecutor.ExcecuteScript passing the python script (or any other supportesd script) for the command
Info

The ScriptCommand class implements Revit’s IExternalCommand interface. The Execute method is the one called when you click the Ribbon button.

In turn, the code in ScriptExecutor.cs calls the appropriate script engine based on the type of (IronPython, CPython, .NET, and so on).

Info

You can find the code of the engines in the files that end with Engine.cs, in dev/pyRevitLabs.PyRevit.Runtime/:

  • IronPythonEngine.cs — default Python engine.
  • CPythonEngine.cs — modern Python (3.12).
  • CLREngine.cs — C#/VB.NET execution.
  • DynamoBIMEngine.cs — Dynamo graphs.
  • GrasshopperEngine.cs — Grasshopper definitions.

Engine cache and Reload

Engines are cached per session in AppDomain data, keyed by engine type id, and a Reload clears that cache through ScriptEngineManager.ClearEngines. ScriptEngine.Shutdown() is therefore a cache eviction, not a process teardown: it runs on Reload and on Refresh Engine, and it must leave anything process-wide alone.

CPythonEngine is the one engine whose host is a process-wide singleton. The CPython interpreter and its pythonnet metatype are started on the first CPython command of the process and stay up until Revit exits, so a Reload just drops the engine object and the next python3 command re-attaches to the live interpreter. pythonnet's own PythonEngine.Shutdown is deliberately never called mid-session: it is a soft shutdown that stashes the CLR metatype state into the still-running interpreter as a BinaryFormatter blob, and that blob cannot be read back once Revit's Reload has duplicated assemblies into its assembly load context. A side effect is that engine: clean on a python3 bundle yields a fresh engine object but not a fresh interpreter, so sys.modules entries imported before a Reload survive it. The contract is asserted by the tests in dev/pyRevitLabs/tests/pyRevitLabs.PyRevit.Runtime.CPython.Tests/.