From 4b4c36df7ac1bb88c29b930eb1d8d5f3fbba35d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Wed, 20 May 2020 20:52:31 +0200 Subject: [PATCH] Add possibility to create multiple plugins object from a single .py file or python module. --- src/runner/pythonrunner.cpp | 124 +++++++++++++++++++++++++++--------- 1 file changed, 95 insertions(+), 29 deletions(-) diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 0717cd7..3e4d8b9 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include "uibasewrappers.h" #include "proxypluginwrappers.h" @@ -815,7 +816,10 @@ private: void appendIfInstance(bpy::object const& obj, QList& interfaces); private: - std::map m_PythonObjects; + + // List of python objects representing plugins to keep all the bpy::object "alive" + // during the execution. + std::vector m_PythonObjects; const MOBase::IOrganizer* m_MOInfo; wchar_t* m_PythonHome; }; @@ -948,6 +952,7 @@ bool PythonRunner::initPython(const QString &pythonPath) bpy::object mainNamespace = mainModule.attr("__dict__"); mainNamespace["sys"] = bpy::import("sys"); mainNamespace["moprivate"] = bpy::import("moprivate"); + mainNamespace["mobase"] = bpy::import("mobase"); bpy::import("site"); bpy::exec("sys.stdout = moprivate.PrintWrapper()\n" "sys.stderr = moprivate.ErrWrapper.instance()\n" @@ -1012,54 +1017,115 @@ void PythonRunner::appendIfInstance(bpy::object const& obj, QList &int QList PythonRunner::instantiate(const QString &pluginName) { + // `pluginName` can either be a python file (single-file plugin or a folder (whole module). + // + // For whole module, we simply add the parent folder to path, then we load the module with a simple + // bpy::import, and we retrieve the associated __dict__ from which we extract either createPlugin or + // createPlugins. + // + // For single file, we need to use bpy::exec_file, and we will use the context (global variables) + // from __main__ (already contains mobase, and other required module). Since the context is shared + // between called of `instantiate`, we need to make sure to remove createPlugin(s) from previous call. try { GILock lock; - bpy::object mainModule = bpy::import("__main__"); - bpy::object moduleNamespace = mainModule.attr("__dict__"); - bpy::object sys = bpy::import("sys"); - moduleNamespace["sys"] = sys; - moduleNamespace["mobase"] = bpy::import("mobase"); + // Dictionary that will contain createPlugin() or createPlugins(). + bpy::dict moduleDict; if (pluginName.endsWith(".py")) { + bpy::object mainModule = bpy::import("__main__"); + bpy::dict moduleNamespace = bpy::extract(mainModule.attr("__dict__"))(); + std::string temp = ToString(pluginName); - if (handled_exec_file(temp.c_str(), moduleNamespace)) { - throw pyexcept::PythonError(); + if (!handled_exec_file(temp.c_str(), moduleNamespace)) { + moduleDict = moduleNamespace; } - m_PythonObjects[pluginName] = moduleNamespace["createPlugin"](); } else { // Retrieve the module name: QStringList parts = pluginName.split("/"); std::string moduleName = ToString(parts.takeLast()); ensureFolderInPath(parts.join("/")); - bpy::object createPlugin = bpy::import(moduleName.c_str()).attr("createPlugin"); - m_PythonObjects[pluginName] = createPlugin(); + moduleDict = bpy::dict(bpy::import(moduleName.c_str()).attr("__dict__")); } - bpy::object pluginObj = m_PythonObjects[pluginName]; - QList interfaceList; + if (bpy::len(moduleDict) == 0) { + MOBase::log::error("Failed to import plugin from {}.", pluginName); + throw pyexcept::PythonError(); + } - appendIfInstance(pluginObj, interfaceList); - // Must try the wrapper because it's only a plugin extension interface in C++, so doesn't extend QObject - appendIfInstance(pluginObj, interfaceList); - // Must try the wrapper because it's only a plugin extension interface in C++, so doesn't extend QObject - appendIfInstance(pluginObj, interfaceList); - appendIfInstance(pluginObj, interfaceList); - appendIfInstance(pluginObj, interfaceList); - appendIfInstance(pluginObj, interfaceList); - appendIfInstance(pluginObj, interfaceList); - appendIfInstance(pluginObj, interfaceList); + // Create the plugins: + std::vector plugins; - if (interfaceList.isEmpty()) - appendIfInstance(pluginObj, interfaceList); + if (moduleDict.has_key("createPlugin")) { + plugins.push_back(moduleDict["createPlugin"]()); - return interfaceList; - } catch (const bpy::error_already_set&) { - qWarning("failed to run python script \"%s\"", qUtf8Printable(pluginName)); + // Clear for future call + bpy::delitem(moduleDict, bpy::str("createPlugin")); + } + else if (moduleDict.has_key("createPlugins")) { + bpy::object pyPlugins = moduleDict["createPlugins"](); + if (!PySequence_Check(pyPlugins.ptr())) { + MOBase::log::error("Plugin {}: createPlugins must return a list.", pluginName); + } + else { + bpy::list pyList(pyPlugins); + int nPlugins = bpy::len(pyList); + for (int i = 0; i < nPlugins; ++i) { + plugins.push_back(pyList[i]); + } + } + + // Clear for future call + bpy::delitem(moduleDict, bpy::str("createPlugins")); + } + else { + MOBase::log::error("Plugin {}: missing a createPlugin(s) function.", pluginName); + } + + // If we have no plugins, there was an issue, and we already logged the problem: + if (plugins.empty()) { + return QList(); + } + + QList allInterfaceList; + + for (bpy::object pluginObj : plugins) { + + // Add the plugin to keep it alive: + m_PythonObjects.push_back(pluginObj); + + QList interfaceList; + + appendIfInstance(pluginObj, interfaceList); + // Must try the wrapper because it's only a plugin extension interface in C++, so doesn't extend QObject + appendIfInstance(pluginObj, interfaceList); + // Must try the wrapper because it's only a plugin extension interface in C++, so doesn't extend QObject + appendIfInstance(pluginObj, interfaceList); + appendIfInstance(pluginObj, interfaceList); + appendIfInstance(pluginObj, interfaceList); + appendIfInstance(pluginObj, interfaceList); + appendIfInstance(pluginObj, interfaceList); + appendIfInstance(pluginObj, interfaceList); + + if (interfaceList.isEmpty()) { + appendIfInstance(pluginObj, interfaceList); + } + + if (interfaceList.isEmpty()) { + MOBase::log::error("Plugin {}: no plugin interface implemented.", pluginName); + } + + // Append the plugins to the main list: + allInterfaceList.append(interfaceList); + } + + return allInterfaceList; + } + catch (const bpy::error_already_set&) { + MOBase::log::error("Failed to import plugin from {}.", pluginName); throw pyexcept::PythonError(); } - return QList(); } bool PythonRunner::isPythonInstalled() const