Skip to content

CPython: pyrevit.forms WPF/XAML support (PythonNet3) #3033

@OriAshkenazi

Description

@OriAshkenazi

TL;DR (decision needed)

CPython scripts (#! python3) currently cannot use pyrevit.forms (the module hard-fails at import time and depends on the IronPython-only wpf/XAML stack).

Please reply:

  1. A or B for the path forward:
    • A) UI broker/proxy (Effort 1): make the existing pyrevit.forms implementation callable from CPython
    • B) Native pythonnet WPF/XAML support (Effort 2): implement/drive upstream support so CPython can run WPF natively
  2. If you agree with (A), can I open a small Stage 0 PR immediately (import works + actionable exceptions), then follow with the broker?
  3. Definition check: for this issue, does "CPython forms support" require:
    • (a) parity for the existing pyrevit.forms API (built-in dialogs/windows), OR
    • (b) also parity for CPython-authored WPFWindow + custom XAML event handlers?

Context

Goal

  • A CPython script can do from pyrevit import forms and call the same pyrevit.forms API that works today under IronPython.
  • If the chosen path is the UI broker (Effort 1), CPython should be able to invoke the existing IronPython/WPF implementation and get structured results back (and interact with returned window objects via proxies).

Proposed plan (2 main efforts)

Effort 1 (recommended near-term): UI broker/proxy so CPython calls the existing IronPython/WPF implementation

This follows the "known good" practice: keep the existing WPF implementation as the source of truth, and have CPython proxy calls into it.

Effort 1 is best done in stages. Stage 0 matches the first "prep" idea discussed in the PR #3029 thread (my comment):

Stage 0 (small PR): make import pyrevit.forms work under CPython

  • Remove the import-time hard guard in pyrevitlib/pyrevit/forms/__init__.py (do not raise on not IRONPY).
  • Isolate IronPython-only imports (wpf, Autodesk UI modules, etc.) so CPython can import without immediately failing.
  • Keep UI functionality IronPython-only for now (raise an actionable exception on call under CPython) until the broker is in place.
  • Add a minimal CPython DevTools smoke command that:
    • imports pyrevit.forms, and
    • verifies at least one representative UI entrypoint fails with a clear "not yet supported under CPython; see this issue" message (instead of ImportError/AttributeError).

Stage 1+ (broker): make the full API callable from CPython by proxying to the IronPython/WPF implementation

  1. Turn pyrevitlib/pyrevit/forms/__init__.py into an engine-agnostic facade

    • Move the current IronPython/WPF implementation into an IronPython-only module (e.g. pyrevitlib/pyrevit/forms/_ipy_forms.py) without changing IronPython behavior.
    • In __init__.py, dispatch:
      • IronPython (IRONPY=True) -> re-export from _ipy_forms.
      • CPython (IRONPY=False) -> re-export from a proxy module _cpy_forms_proxy.
  2. Add a broker entrypoint on the .NET side (so both engines can reach it)

    Proposed boundary:

    string Invoke(string requestJson); // returns responseJson

    Notes/constraints:

    • JSON alone can not carry live Revit/.NET objects (Elements, UIApplication, WPF objects, delegates).
    • Plan: use an object-handle registry in the broker/handler for any non-JSON-serializable value. Requests/responses pass { kind: "handle", id: "..." } for those values.
    • Keep a strict allowlist of callable functions/methods and validate payloads to avoid turning this into "remote eval".
    • Execute UI actions on the Revit UI thread (Dispatcher/ExternalEvent) and return structured errors.
  3. Implement the IronPython handler (execute real pyrevit.forms calls)

    • Import and execute the existing IronPython/WPF implementation.
    • Support:
      • function calls by name + args/kwargs
      • object lifecycle for WPF windows (create -> return handle id; later calls via handle id)
    • Definition check tie-in: if maintainers want CPython-authored WPFWindow + event handlers parity, this also needs a callback story (UI events firing in IronPython invoking CPython callbacks through a safe, explicit bridge). Otherwise, initial scope can be: "CPython can call the built-in forms API, including windows created by that API".
  4. Implement the CPython proxy module

    • pyrevitlib/pyrevit/forms/_cpy_forms_proxy.py provides the same API surface, but each call serializes a request (name/args/kwargs) to the broker and deserializes the response.
    • For returned WPF objects, return lightweight Python proxy objects that forward method/property calls back to the broker using the handle id.
  5. Add a small DevTools end-to-end smoke command

    • Add a CPython command under extensions/pyRevitDevTools.extension that exercises representative calls end-to-end (alert/select list + at least one WPFWindow-based dialog that is part of pyrevit.forms).

Effort 2 (larger + upstream dependent): native CPython WPF/XAML support

Summary of what we know so far (Discourse saga + repo references):

  • In WIP pyRevit, pythonnet was updated to v3, which improves CPython integration but still has no WPF support out-of-the-box. In the saga thread, @sanzoghenzo documented an attempted path to make pyrevit.forms work under CPython by tackling the wpf.LoadComponent gap.
  • Removing the IRONPY guard in pyrevitlib/pyrevit/forms/__init__.py quickly hits:
    • Autodesk-only imports (Autodesk.Windows.ComponentManager) used by forms.show_balloon.
    • The core blocker: wpf is None under CPython because pyrevitlib/pyrevit/framework.py only loads the IronPython wpf module when IRONPY is true. WPFWindow.load_xaml relies on wpf.LoadComponent.
  • The thread highlights that LoadComponent is effectively the key missing primitive: under IronPython it comes from IronPython.Wpf and uses the DLR DynamicXamlReader + DynamicOperations to:
    • populate x:Name fields on the Python window instance
    • hook up XAML event handlers
    • reference implementation lives in this repo under dev/modules/pyRevitLabs.ironpython3/Src/DLR/Src/Microsoft.Dynamic/Xaml/DynamicXamlReader.cs (depends on DLR dynamic ops that pythonnet does not provide)
  • An attempted Python port of DynamicXamlReader using System.Xaml (XamlXmlReader, XamlObjectWriter, custom XamlMemberInvoker) got further but failed early when loading pyrevitlib/pyrevit/forms/GetValueWindow.xaml with:
    • Set property "System.Windows.Window.ShowInTaskbar" threw an exception
      This suggests additional WPF/XAML loading semantics need to be replicated correctly (property setting, root instance handling, name scopes, event binding, etc.).
  • @sanzoghenzo shared a WIP branch with the refactor/experiments: https://github.com/sanzoghenzo/pyRevit/tree/forms-refactor
  • The repo also includes pythonnet WPF demos (not pyRevit-integrated) such as dev/modules/pyRevitLabs.Python.Net/demo/DynamicGrid.py that load XAML via System.Windows.Markup.XamlReader.Load on an STA thread.
  • I did not find any repo-local pointers to the mentioned "McNeel/Dynamo pythonnet WPF patches" beyond @sanzoghenzo's note in the PR PyRevitRunner: honor cpython hashbang #3029 thread; no first-party code/docs/issues in this repo reference those patches explicitly.
  • Quote for context (from @sanzoghenzo in the PR PyRevitRunner: honor cpython hashbang #3029 thread): "Ehsan (@eirannejad) told us about pythonnet patches made internally by mcneel, same thing by dynamo guys, I don't know if any of it reach upstream..."

Given the above, Effort 2 likely depends on upstream/downstream pythonnet work. Before investing heavily, it would help to identify:

  • Is there an upstream pythonnet issue/branch for WPF/XAML parity?
  • Are McNeel/Dynamo patches published, and can they be upstreamed/adopted?

Next step after decision

  • If maintainers prefer (A), I can start with Stage 0 immediately, then follow with the facade + broker skeleton and a small CPython DevTools end-to-end smoke test.
  • If maintainers prefer (B), I can help track down the relevant upstream pythonnet work/pointers first (issues/branches/patches), before starting a large in-repo WPF implementation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions