Skip to content
Closed

test #1371

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,7 @@ about_rv.cpp

# Dear ImgGui generated
imgui.ini

# Local golden-test media paths (see src/test/golden/session_manager/fixtures/mp4.env.example)
src/test/golden/session_manager/fixtures/mp4.env
src/test/golden/layer_select/fixtures/layers.env
103 changes: 100 additions & 3 deletions src/lib/app/mu_rvui/mode_manager.mu
Original file line number Diff line number Diff line change
Expand Up @@ -295,8 +295,70 @@ class: ModeManagerMode : MinorMode
return if is_nil(pymode) then nil else PyMinorMode(pymode);
}

method: requestedModeImpl (string; ModeEntry entry)
{
//
// "python" or "mu" when the environment asks for a specific
// implementation of a mode, nil when it does not care and the choice is
// left to whatever the package actually ships. Asking explicitly is what
// lets a Mu->Python port be verified against the Mu behavior while both
// sources are still in the build: the migration's Mu-baseline gate runs
// with RV_MODE_IMPL_<mode>=mu.
//
use path;

let base = without_extension(entry.name),
impl = getenv("RV_MODE_IMPL_%s" % base, nil);

if (impl neq nil) return if impl == "python" then "python" else "mu";

let prefer = getenv("RV_PREFER_PYTHON_MODES", nil);

if (prefer neq nil)
{
for_each (name; prefer.split(",")) if (name == base) return "python";
}

return nil;
}

method: pythonModuleExists (bool; string name)
{
//
// Whether a Python implementation of this mode is importable, asked
// without importing it and without leaving a Python error set.
//
// It has to be this indirect. python.PyImport_Import() calls
// PyErr_Print() when the module is missing, and most modes are Mu-only,
// so probing by import would print a traceback for every one of them at
// startup. find_spec() reports a missing module as None rather than as an
// error, and None arrives here as a non-nil PyObject, so the answer is in
// the type rather than in is_nil().
//
try
{
let util = PyImport_Import("importlib.util");

if (is_nil(util)) return false;

let finder = PyObject_GetAttr(util, "find_spec");

if (is_nil(finder)) return false;

let spec = PyObject_CallObject(finder, name);

return !is_nil(spec) && type_name(Py_TYPE(spec)) != "NoneType";
}
catch (...)
{
return false;
}
}

method: loadEntry (void; ModeEntry entry)
{
use path;

if (!entry.loaded)
{
State state = data();
Expand All @@ -316,8 +378,21 @@ class: ModeManagerMode : MinorMode
}
let loadStartTime = theTime();
PyMinorMode pymode = nil;

if (!runtime.load_module(entry.name))
let requested = requestedModeImpl(entry);

//
// Python is the default whenever the package ships a Python
// implementation, including when it also still ships a Mu one: the Mu
// modes are being retired package by package, and a ported package
// keeps its .mu source only so the migration's Mu-baseline gate can
// still run against it. So the question is not "is Python being asked
// for" but "is there a Python implementation at all".
//
let usePython = if requested neq nil
then requested == "python"
else pythonModuleExists(path.without_extension(entry.name));

if (usePython)
{
try
{
Expand All @@ -328,11 +403,33 @@ class: ModeManagerMode : MinorMode
print("ERROR: while loading python module: %s\n" % exc);
}

if (pymode eq nil) throw exception("failed in runtime.load_module");
//
// A Python implementation that exists but does not load is an
// error, and deliberately NOT a quiet fall-through to Mu.
//
// Falling back looks appealing but is unsound: a Python mode
// registers itself with defineMinorMode() from inside init(), which
// runs near the top of its constructor, so a failure anywhere after
// that point leaves the mode already registered. Loading the Mu
// module next makes its own defineMinorMode() throw "Duplicate
// mode", and that exception is caught upstream and routed to
// showWarning(), which is silent unless -ModeManagerVerbose. The
// net effect was no mode at all and no message — strictly worse
// than reporting the original failure here.
//
if (pymode eq nil)
{
throw exception("python implementation of %s could not be loaded" % entry.name);
}
}

if (pymode eq nil)
{
if (!runtime.load_module(entry.name))
{
throw exception("failed in runtime.load_module");
}

if (_verbose)
{
let foundIt = false;
Expand Down
4 changes: 4 additions & 0 deletions src/lib/app/py_rvui/rv/qtutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
from PySide6 import QtGui, QtWidgets
from PySide6.QtGui import *
from PySide6.QtWidgets import *

# Qt6 moved QOpenGLWidget out of QtWidgets, so sessionGLView() below
# cannot get it from the star imports.
from PySide6.QtOpenGLWidgets import QOpenGLWidget
from shiboken6 import wrapInstance
from shiboken6 import getCppPointer
except ImportError:
Expand Down
86 changes: 77 additions & 9 deletions src/plugins/rv-packages/maya_tools/maya_tools.mu.in
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,77 @@ use app_utils;
require io;
require extra_commands;
require runtime;
require session_manager;
require rvui;
require python;

require qt;

//
// Reaching the session manager from Mu.
//
// The session manager is a Python package now, and a Mu `require` cannot resolve a
// Python module, so this file no longer requires it. Whether it is loaded is
// answered by minorModeFromName(), which finds a Python mode too (it is registered
// as a PyMinorMode carrying the same mode name), and the tree selection is served
// as an internal event that either implementation answers with one node per line.
//

\: sessionManagerLoaded (bool;)
{
return rvui.minorModeFromName("session_manager") neq nil;
}

\: sessionManagerSelectedNodes (string[];)
{
string[] nodes;
string content = "";

//
// Ask the loaded Python mode directly. This is what preserves the original
// semantics: the Mu code this replaced held the mode object and read its tree
// view, which exists from the constructor onward, so the selection was
// readable whether or not the panel was open. RV only dispatches internal
// events to *active* modes, and session_manager is `load: delay`, so relying on
// the event alone reported an empty selection whenever the panel was closed --
// which silently flipped the menu states below.
//
try
{
let mod = python.PyImport_Import("session_manager");

if (!python.is_nil(mod))
{
let f = python.PyObject_GetAttr(mod, "selectedNodeLines");

if (!python.is_nil(f))
{
content = python.to_string(
python.PyObject_CallObject(f, python.PyTuple_New(0)));
}
}
}
catch (...)
{
content = "";
}

//
// Empty means the Python implementation is not the one loaded (its module-level
// mode is nil). The Mu implementation answers the internal event instead, which
// requires it to be active.
//
if (content == "")
{
content = commands.sendInternalEvent("session-manager-selected-nodes", "", "");
}

if (content == "") return nodes;

for_each (n; content.split("\n")) if (n != "") nodes.push_back(n);

return nodes;
}

require io;

function: deb(void; string s) { if (false) print (s + "\n"); }
Expand Down Expand Up @@ -156,10 +224,10 @@ class: MayaTools : MinorMode

method: compareSelected (void; Event event, string style)
{
if (session_manager.theMode() eq nil) return;
if (!sessionManagerLoaded()) return;

let nodeType = if (style == "tile") then "RVLayoutGroup" else "RVStackGroup",
selected = session_manager.theMode().selectedNodes();
selected = sessionManagerSelectedNodes();

if (selected.size() < 2) return;

Expand Down Expand Up @@ -213,18 +281,18 @@ class: MayaTools : MinorMode

method: oneViewSelected (int; )
{
if (session_manager.theMode() eq nil) return DisabledMenuState;
if (!sessionManagerLoaded()) return DisabledMenuState;

let nodes = session_manager.theMode().selectedNodes();
let nodes = sessionManagerSelectedNodes();

return if (nodes.size() == 1) then NeutralMenuState else DisabledMenuState;
}

method: viewsSelected (int; )
{
if (session_manager.theMode() eq nil) return DisabledMenuState;
if (!sessionManagerLoaded()) return DisabledMenuState;

let nodes = session_manager.theMode().selectedNodes();
let nodes = sessionManagerSelectedNodes();

return if (nodes.size() >= 2) then NeutralMenuState else DisabledMenuState;
}
Expand Down Expand Up @@ -261,9 +329,9 @@ class: MayaTools : MinorMode

method: markCurrentAsTarget (void; Event event)
{
if (session_manager.theMode() eq nil) return;
if (!sessionManagerLoaded()) return;

let nodes = session_manager.theMode().selectedNodes();
let nodes = sessionManagerSelectedNodes();

if (nodes.size() != 1) return;

Expand Down
Loading