mirror of
https://github.com/ModOrganizer2/modorganizer-plugin_python.git
synced 2026-07-27 14:03:33 -07:00
@@ -0,0 +1,21 @@
|
||||
---
|
||||
# We'll use defaults from the LLVM style, but with 4 columns indentation.
|
||||
BasedOnStyle: LLVM
|
||||
IndentWidth: 4
|
||||
---
|
||||
Language: Cpp
|
||||
# Force pointers to the type for C++.
|
||||
DerivePointerAlignment: false
|
||||
PointerAlignment: Left
|
||||
AlignConsecutiveAssignments: true
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLambdasOnASingleLine: Empty
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
AccessModifierOffset: -4
|
||||
AlignTrailingComments: true
|
||||
SpacesBeforeTrailingComments: 2
|
||||
NamespaceIndentation: All
|
||||
MaxEmptyLinesToKeep: 1
|
||||
BreakBeforeBraces: Stroustrup
|
||||
ColumnLimit: 88
|
||||
@@ -1,5 +1,12 @@
|
||||
# build
|
||||
edit
|
||||
CMakeLists.txt.user
|
||||
/msbuild.log
|
||||
/*std*.log
|
||||
/*build
|
||||
|
||||
# python
|
||||
tests/**/__pycache__
|
||||
|
||||
# IDE
|
||||
.vscode
|
||||
|
||||
@@ -6,8 +6,30 @@ else()
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake_common/mo2.cmake)
|
||||
endif()
|
||||
|
||||
set(PYTHON_BUILD_PATH ${PYTHON_ROOT}/PCBuild/amd64)
|
||||
set(PYVERSION 310)
|
||||
|
||||
set(PYBIND11_FINDPYTHON true)
|
||||
set(PYTHON_EXECUTABLE ${PYTHON_BUILD_PATH}/python.exe)
|
||||
set(PYTHON_INCLUDE_DIRS ${PYTHON_ROOT}/Include)
|
||||
set(PYTHON_LIBRARIES ${MO2_INSTALL_LIBS_PATH}/python${PYVERSION}.lib)
|
||||
|
||||
add_subdirectory(${MO2_BUILD_PATH}/pybind11 ${CMAKE_CURRENT_BINARY_DIR}/pybind11)
|
||||
|
||||
project(plugin_python)
|
||||
|
||||
# order matters!
|
||||
add_subdirectory(src/pybind11-qt)
|
||||
add_subdirectory(src/mobase)
|
||||
add_subdirectory(src/runner)
|
||||
add_subdirectory(src/proxy)
|
||||
|
||||
# force plugin_python to build mobase
|
||||
add_dependencies(plugin_python mobase)
|
||||
set_property(DIRECTORY ${PROJECT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT plugin_python)
|
||||
|
||||
set(PLUGIN_PYTHON_TESTS ${PLUGIN_PYTHON_TESTS} CACHE BOOL "build tests for plugin_python")
|
||||
if (PLUGIN_PYTHON_TESTS)
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
pybind11_add_module(mobase MODULE)
|
||||
mo2_configure_library(mobase
|
||||
SOURCE_TREE
|
||||
WARNINGS OFF
|
||||
AUTOMOC ON
|
||||
TRANSLATIONS OFF
|
||||
PRIVATE_DEPENDS uibase Qt::Core
|
||||
)
|
||||
target_link_libraries(mobase PRIVATE pybind11::qt)
|
||||
target_include_directories(mobase PRIVATE ${PYTHON_ROOT}/Include)
|
||||
|
||||
install(TARGETS mobase DESTINATION bin/plugins/plugin_python/libs)
|
||||
@@ -0,0 +1,54 @@
|
||||
#include "deprecation.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <set>
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
namespace mo2::python {
|
||||
|
||||
void show_deprecation_warning(std::string_view name, std::string_view message,
|
||||
bool show_once)
|
||||
{
|
||||
|
||||
// Contains the list of filename / line number for which a deprecation
|
||||
// warning has already been shown.
|
||||
static std::set<std::pair<std::string, int>> DeprecatedLines;
|
||||
|
||||
// Find the caller:
|
||||
auto inspect = py::module_::import("inspect");
|
||||
auto current_frame = inspect.attr("currentframe")();
|
||||
py::sequence callable_frame = inspect.attr("getouterframes")(current_frame, 2);
|
||||
auto filename = callable_frame[-1].attr("filename").cast<std::string>();
|
||||
auto function = callable_frame[-1].attr("function").cast<std::string>();
|
||||
auto lineno = callable_frame[-1].attr("lineno").cast<int>();
|
||||
|
||||
// Only show once if requested:
|
||||
if (show_once && DeprecatedLines.contains({filename, lineno})) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Register the deprecation:
|
||||
DeprecatedLines.emplace(filename, lineno);
|
||||
|
||||
auto path = relative(std::filesystem::path(filename),
|
||||
QCoreApplication::applicationDirPath().toStdWString());
|
||||
|
||||
// Show the message:
|
||||
if (message.empty()) {
|
||||
MOBase::log::warn("[deprecated] {} in {} [{}:{}].", name, function,
|
||||
path.native(), lineno);
|
||||
}
|
||||
else {
|
||||
MOBase::log::warn("[deprecated] {} in {} [{}:{}]: {}", name, function,
|
||||
path.native(), lineno, message);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mo2::python
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef PYTHONRUNNER_UTILS_H
|
||||
#define PYTHONRUNNER_UTILS_H
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
namespace mo2::python {
|
||||
|
||||
/**
|
||||
* @brief Show a deprecation warning.
|
||||
*
|
||||
* This methods will print a warning in MO2 log containing the location of
|
||||
* the call to the deprecated function. If show_once is true, the
|
||||
* deprecation warning will only be logged the first time the function is
|
||||
* called at this location.
|
||||
*
|
||||
* @param name Name of the deprecated function.
|
||||
* @param message Deprecation message.
|
||||
* @param show_once Only show the message once per call location.
|
||||
*/
|
||||
void show_deprecation_warning(std::string_view name, std::string_view message = "",
|
||||
bool show_once = true);
|
||||
|
||||
} // namespace mo2::python
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,82 @@
|
||||
#pragma warning(disable : 4100)
|
||||
#pragma warning(disable : 4996)
|
||||
|
||||
#include <tuple>
|
||||
#include <variant>
|
||||
|
||||
#include "pybind11_all.h"
|
||||
|
||||
#include "wrappers/pyfiletree.h"
|
||||
#include "wrappers/wrappers.h"
|
||||
|
||||
// TODO: remove these include (only for testing)
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QWidget>
|
||||
#include <fmt/format.h>
|
||||
#include <iplugin.h>
|
||||
#include <iplugindiagnose.h>
|
||||
#include <ipluginfilemapper.h>
|
||||
#include <iplugingame.h>
|
||||
#include <iplugininstaller.h>
|
||||
#include <iplugininstallersimple.h>
|
||||
#include <ipluginlist.h>
|
||||
#include <ipluginmodpage.h>
|
||||
#include <ipluginpreview.h>
|
||||
#include <iplugintool.h>
|
||||
#include <isavegame.h>
|
||||
#include <isavegameinfowidget.h>
|
||||
|
||||
using namespace MOBase;
|
||||
namespace py = pybind11;
|
||||
|
||||
PYBIND11_MODULE(mobase, m)
|
||||
{
|
||||
using namespace mo2::python;
|
||||
|
||||
py::module_::import("PyQt6.QtCore");
|
||||
py::module_::import("PyQt6.QtWidgets");
|
||||
|
||||
// bindings
|
||||
//
|
||||
mo2::python::add_basic_bindings(m);
|
||||
mo2::python::add_wrapper_bindings(m);
|
||||
|
||||
// game features must be added before plugins
|
||||
mo2::python::add_game_feature_bindings(m);
|
||||
|
||||
mo2::python::add_plugins_bindings(m);
|
||||
|
||||
// widgets
|
||||
//
|
||||
py::module_ widgets(
|
||||
py::reinterpret_borrow<py::module_>(PyImport_AddModule("mobase.widgets")));
|
||||
m.attr("widgets") = widgets;
|
||||
mo2::python::add_widget_bindings(widgets);
|
||||
|
||||
// functions
|
||||
//
|
||||
m.def("getFileVersion", &MOBase::getFileVersion, py::arg("filepath"));
|
||||
m.def("getProductVersion", &MOBase::getProductVersion, py::arg("executable"));
|
||||
m.def("getIconForExecutable", &MOBase::iconForExecutable, py::arg("executable"));
|
||||
|
||||
// expose MoVariant - MoVariant is a fake object whose only purpose is to be
|
||||
// used as a type-hint on the python side (e.g., def foo(x:
|
||||
// mobase.MoVariant))
|
||||
//
|
||||
// the real MoVariant is defined in the generated stubs, since it's only
|
||||
// relevant when doing type-checking, but this needs to be defined,
|
||||
// otherwise MoVariant is not found when actually running plugins through
|
||||
// MO2, making them crash
|
||||
m.attr("MoVariant") = py::none();
|
||||
|
||||
// private stuff for debugging/test
|
||||
py::module_ moprivate(
|
||||
py::reinterpret_borrow<py::module_>(PyImport_AddModule("mobase.private")));
|
||||
m.attr("private") = moprivate;
|
||||
|
||||
// expose a function to create a particular tree, only for debugging
|
||||
// purpose, not in mobase.
|
||||
mo2::python::add_make_tree_function(moprivate);
|
||||
moprivate.def("extract_plugins", &mo2::python::extract_plugins);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#ifndef PYTHON_PYBIND11_ALL_H
|
||||
#define PYTHON_PYBIND11_ALL_H
|
||||
|
||||
#include <pybind11/operators.h>
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/stl.h>
|
||||
#include <pybind11/stl/filesystem.h>
|
||||
|
||||
#include "pybind11_utils/functional.h"
|
||||
|
||||
#include "pybind11_qt/pybind11_qt.h"
|
||||
|
||||
#include "pybind11_utils/shared_cpp_owner.h"
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,7 @@
|
||||
#ifndef PYTHON_PYBIND11_FUNCTIONAL_H
|
||||
#define PYTHON_PYBIND11_FUNCTIONAL_H
|
||||
|
||||
// TODO
|
||||
#include <pybind11/functional.h>
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,98 @@
|
||||
#ifndef PYTHON_PYBIND11_SHARED_CPP_OWNER_H
|
||||
#define PYTHON_PYBIND11_SHARED_CPP_OWNER_H
|
||||
|
||||
// pybind11 has some issues when a Python classes extend a C++ wrapper since the Python
|
||||
// object is not kept alive alongside the returned object
|
||||
//
|
||||
// there is a pybind11 branch called "smart_holder" that tries to solve this in a very
|
||||
// complicated way (with many other features)
|
||||
//
|
||||
// here, we simply use a custom type_caster<> for the classes we need - see the actual
|
||||
// definition in mo2::python::detail below
|
||||
//
|
||||
// IMPORTANT: this only works for classes that are managed by shared_ptr on the C++
|
||||
// side, not Qt object
|
||||
//
|
||||
|
||||
// TODO: WIP for Qt object
|
||||
|
||||
namespace mo2::python::detail {
|
||||
|
||||
template <class Type, class SharedType>
|
||||
struct shared_cpp_owner_caster
|
||||
: pybind11::detail::copyable_holder_caster<Type, SharedType> {
|
||||
|
||||
// note that the actual holder type might be different in term of constness
|
||||
using type = Type;
|
||||
using holder_type = SharedType;
|
||||
|
||||
using base = pybind11::detail::copyable_holder_caster<Type, SharedType>;
|
||||
using base::holder;
|
||||
using base::value;
|
||||
|
||||
// in load, we use the default type_caster<> to extract the shared pointer, then
|
||||
// we replace it by a custom one
|
||||
//
|
||||
// the custom shared_ptr<> holds the py::object BUT does not really manage the
|
||||
// C++ object because it will ref-count but not delete it
|
||||
//
|
||||
// this should work because here it's how it works:
|
||||
// - the Python object holds a standard shared_ptr<> for the C++ object -> the
|
||||
// C++ object remains alive as long as the Python one remains alive
|
||||
// - the C++ object holds a shared_ptr<> that manages the python object -> the
|
||||
// Python object remains alive as-long as there is a shared_ptr<> on the C++
|
||||
// side
|
||||
//
|
||||
bool load(pybind11::handle src, bool convert)
|
||||
{
|
||||
namespace py = pybind11;
|
||||
|
||||
if (!base::load(src, convert)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
holder.reset(holder.get(), [pyobj = py::reinterpret_borrow<py::object>(
|
||||
src)](auto*) mutable {
|
||||
py::gil_scoped_acquire s;
|
||||
pyobj = std::move(py::none());
|
||||
|
||||
// we do NOT delete the object here - if this was the last reference to
|
||||
// the Python object, the Python object will delete it
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// cast simply forward to the original type_caster<>
|
||||
//
|
||||
static pybind11::handle cast(const holder_type& src,
|
||||
pybind11::return_value_policy policy,
|
||||
pybind11::handle parent)
|
||||
{
|
||||
return base::cast(src, policy, parent);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mo2::python::detail
|
||||
|
||||
#define MO2_PYBIND11_SHARED_CPP_HOLDER(Type) \
|
||||
namespace pybind11::detail { \
|
||||
template <> \
|
||||
struct type_caster<std::shared_ptr<Type>> \
|
||||
: mo2::python::detail::shared_cpp_owner_caster<Type, \
|
||||
std::shared_ptr<Type>> { \
|
||||
}; \
|
||||
template <> \
|
||||
struct type_caster<std::shared_ptr<const Type>> \
|
||||
: mo2::python::detail::shared_cpp_owner_caster< \
|
||||
Type, std::shared_ptr<const Type>> { \
|
||||
}; \
|
||||
}
|
||||
|
||||
#include <isavegame.h>
|
||||
#include <pluginrequirements.h>
|
||||
|
||||
MO2_PYBIND11_SHARED_CPP_HOLDER(MOBase::IPluginRequirement)
|
||||
MO2_PYBIND11_SHARED_CPP_HOLDER(MOBase::ISaveGame)
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,377 @@
|
||||
#include "wrappers.h"
|
||||
|
||||
#include <tuple>
|
||||
|
||||
#include "../pybind11_all.h"
|
||||
|
||||
#include <ipluginlist.h>
|
||||
#include <isavegameinfowidget.h>
|
||||
|
||||
#include <bsainvalidation.h>
|
||||
#include <dataarchives.h>
|
||||
#include <gameplugins.h>
|
||||
#include <localsavegames.h>
|
||||
#include <moddatachecker.h>
|
||||
#include <moddatacontent.h>
|
||||
#include <savegameinfo.h>
|
||||
#include <scriptextender.h>
|
||||
#include <unmanagedmods.h>
|
||||
|
||||
#include "pyfiletree.h"
|
||||
|
||||
namespace py = pybind11;
|
||||
using namespace MOBase;
|
||||
using namespace pybind11::literals;
|
||||
|
||||
namespace mo2::python {
|
||||
|
||||
class PyBSAInvalidation : public BSAInvalidation {
|
||||
public:
|
||||
bool isInvalidationBSA(const QString& bsaName) override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(bool, BSAInvalidation, isInvalidationBSA, bsaName);
|
||||
}
|
||||
void deactivate(MOBase::IProfile* profile) override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(void, BSAInvalidation, deactivate, profile);
|
||||
}
|
||||
void activate(MOBase::IProfile* profile) override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(void, BSAInvalidation, activate, profile);
|
||||
}
|
||||
bool prepareProfile(MOBase::IProfile* profile) override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(bool, BSAInvalidation, prepareProfile, profile);
|
||||
}
|
||||
};
|
||||
|
||||
class PyDataArchives : public DataArchives {
|
||||
public:
|
||||
QStringList vanillaArchives() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QStringList, DataArchives, vanillaArchives, );
|
||||
}
|
||||
QStringList archives(const MOBase::IProfile* profile) const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QStringList, DataArchives, archives, profile);
|
||||
}
|
||||
void addArchive(MOBase::IProfile* profile, int index,
|
||||
const QString& archiveName) override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(void, DataArchives, addArchive, profile, index,
|
||||
archiveName);
|
||||
}
|
||||
void removeArchive(MOBase::IProfile* profile,
|
||||
const QString& archiveName) override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(void, DataArchives, removeArchive, profile,
|
||||
archiveName);
|
||||
}
|
||||
};
|
||||
|
||||
class PyGamePlugins : public GamePlugins {
|
||||
public:
|
||||
void writePluginLists(const MOBase::IPluginList* pluginList) override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(void, GamePlugins, writePluginLists, pluginList);
|
||||
}
|
||||
void readPluginLists(MOBase::IPluginList* pluginList) override
|
||||
{
|
||||
// TODO: cannot update plugin list or create one from Python so this is
|
||||
// useless
|
||||
PYBIND11_OVERRIDE_PURE(void, GamePlugins, readPluginLists, pluginList);
|
||||
}
|
||||
QStringList getLoadOrder() override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QStringList, GamePlugins, getLoadOrder, );
|
||||
}
|
||||
bool lightPluginsAreSupported() override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(bool, GamePlugins, lightPluginsAreSupported, );
|
||||
}
|
||||
};
|
||||
|
||||
class PyLocalSavegames : public LocalSavegames {
|
||||
public:
|
||||
MappingType mappings(const QDir& profileSaveDir) const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(MappingType, LocalSavegames, mappings,
|
||||
profileSaveDir);
|
||||
}
|
||||
bool prepareProfile(MOBase::IProfile* profile) override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(bool, LocalSavegames, prepareProfile, profile);
|
||||
}
|
||||
};
|
||||
|
||||
class PyModDataChecker : public ModDataChecker {
|
||||
public:
|
||||
CheckReturn
|
||||
dataLooksValid(std::shared_ptr<const MOBase::IFileTree> fileTree) const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(CheckReturn, ModDataChecker, dataLooksValid,
|
||||
fileTree);
|
||||
}
|
||||
|
||||
std::shared_ptr<MOBase::IFileTree>
|
||||
fix(std::shared_ptr<MOBase::IFileTree> fileTree) const override
|
||||
{
|
||||
PYBIND11_OVERRIDE(std::shared_ptr<MOBase::IFileTree>, ModDataChecker, fix,
|
||||
fileTree);
|
||||
}
|
||||
};
|
||||
|
||||
class PyModDataContent : public ModDataContent {
|
||||
public:
|
||||
std::vector<Content> getAllContents() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(std::vector<Content>, ModDataContent,
|
||||
getAllContents, );
|
||||
;
|
||||
}
|
||||
std::vector<int>
|
||||
getContentsFor(std::shared_ptr<const MOBase::IFileTree> fileTree) const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(std::vector<int>, ModDataContent, getContentsFor,
|
||||
fileTree);
|
||||
}
|
||||
};
|
||||
|
||||
class PySaveGameInfo : public SaveGameInfo {
|
||||
public:
|
||||
MissingAssets getMissingAssets(MOBase::ISaveGame const& save) const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(MissingAssets, SaveGameInfo, getMissingAssets,
|
||||
&save);
|
||||
}
|
||||
ISaveGameInfoWidget* getSaveGameWidget(QWidget* parent = 0) const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(ISaveGameInfoWidget*, SaveGameInfo,
|
||||
getSaveGameWidget, parent);
|
||||
}
|
||||
};
|
||||
|
||||
class PyScriptExtender : public ScriptExtender {
|
||||
public:
|
||||
QString BinaryName() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QString, ScriptExtender, BinaryName, );
|
||||
}
|
||||
|
||||
QString PluginPath() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QString, ScriptExtender, PluginPath, );
|
||||
}
|
||||
|
||||
QString loaderName() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QString, ScriptExtender, loaderName, );
|
||||
}
|
||||
|
||||
QString loaderPath() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QString, ScriptExtender, loaderPath, );
|
||||
}
|
||||
|
||||
QString savegameExtension() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QString, ScriptExtender, savegameExtension, );
|
||||
}
|
||||
|
||||
bool isInstalled() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(bool, ScriptExtender, isInstalled, );
|
||||
}
|
||||
|
||||
QString getExtenderVersion() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QString, ScriptExtender, getExtenderVersion, );
|
||||
}
|
||||
|
||||
WORD getArch() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(WORD, ScriptExtender, getArch, );
|
||||
}
|
||||
};
|
||||
|
||||
class PyPyUnmanagedMods : public UnmanagedMods {
|
||||
public:
|
||||
QStringList mods(bool onlyOfficial) const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QStringList, UnmanagedMods, mods, onlyOfficial);
|
||||
}
|
||||
QString displayName(const QString& modName) const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QString, UnmanagedMods, displayName, modName);
|
||||
}
|
||||
QFileInfo referenceFile(const QString& modName) const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QFileInfo, UnmanagedMods, referenceFile, modName);
|
||||
}
|
||||
QStringList secondaryFiles(const QString& modName) const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QStringList, UnmanagedMods, secondaryFiles, modName);
|
||||
}
|
||||
};
|
||||
|
||||
void add_game_feature_bindings(pybind11::module_ m)
|
||||
{
|
||||
// BSAInvalidation
|
||||
|
||||
py::class_<BSAInvalidation, PyBSAInvalidation>(m, "BSAInvalidation")
|
||||
.def(py::init<>())
|
||||
.def("isInvalidationBSA", &BSAInvalidation::isInvalidationBSA, "name"_a)
|
||||
.def("deactivate", &BSAInvalidation::deactivate, "profile"_a)
|
||||
.def("activate", &BSAInvalidation::activate, "profile"_a);
|
||||
|
||||
// DataArchives
|
||||
|
||||
py::class_<DataArchives, PyDataArchives>(m, "DataArchives")
|
||||
.def(py::init<>())
|
||||
.def("vanillaArchives", &DataArchives::vanillaArchives)
|
||||
.def("archives", &DataArchives::archives, "profile"_a)
|
||||
.def("addArchive", &DataArchives::addArchive,
|
||||
("profile"_a, "index", "name"))
|
||||
.def("removeArchive", &DataArchives::removeArchive, "profile"_a, "name"_a);
|
||||
|
||||
// GamePlugins
|
||||
|
||||
py::class_<GamePlugins, PyGamePlugins>(m, "GamePlugins")
|
||||
.def(py::init<>())
|
||||
.def("writePluginLists", &GamePlugins::writePluginLists, "plugin_list"_a)
|
||||
.def("readPluginLists", &GamePlugins::readPluginLists, "plugin_list"_a)
|
||||
.def("getLoadOrder", &GamePlugins::getLoadOrder)
|
||||
.def("lightPluginsAreSupported", &GamePlugins::lightPluginsAreSupported);
|
||||
|
||||
// LocalSavegames
|
||||
|
||||
py::class_<LocalSavegames, PyLocalSavegames>(m, "LocalSavegames")
|
||||
.def(py::init<>())
|
||||
.def("mappings", &LocalSavegames::mappings, "profile_save_dir"_a)
|
||||
.def("prepareProfile", &LocalSavegames::prepareProfile, "profile"_a);
|
||||
|
||||
// ModDataChecker
|
||||
|
||||
py::class_<ModDataChecker, PyModDataChecker> pyModDataChecker(m,
|
||||
"ModDataChecker");
|
||||
|
||||
py::enum_<ModDataChecker::CheckReturn>(pyModDataChecker, "CheckReturn")
|
||||
.value("INVALID", ModDataChecker::CheckReturn::INVALID)
|
||||
.value("FIXABLE", ModDataChecker::CheckReturn::FIXABLE)
|
||||
.value("VALID", ModDataChecker::CheckReturn::VALID)
|
||||
.export_values();
|
||||
|
||||
pyModDataChecker.def(py::init<>())
|
||||
.def("dataLooksValid", &ModDataChecker::dataLooksValid, "filetree"_a)
|
||||
.def("fix", &ModDataChecker::fix, "filetree"_a);
|
||||
|
||||
// ModDataContent
|
||||
py::class_<ModDataContent, PyModDataContent> pyModDataContent(m,
|
||||
"ModDataContent");
|
||||
|
||||
py::class_<ModDataContent::Content>(pyModDataContent, "Content")
|
||||
.def(py::init<int, QString, QString, bool>(), "id"_a, "name"_a, "icon"_a,
|
||||
"filter_only"_a = false)
|
||||
.def_property_readonly("id", &ModDataContent::Content::id)
|
||||
.def_property_readonly("name", &ModDataContent::Content::name)
|
||||
.def_property_readonly("icon", &ModDataContent::Content::icon)
|
||||
.def("isOnlyForFilter", &ModDataContent::Content::isOnlyForFilter);
|
||||
|
||||
pyModDataContent.def(py::init<>())
|
||||
.def("getAllContents", &ModDataContent::getAllContents)
|
||||
.def("getContentsFor", &ModDataContent::getContentsFor, "filetree"_a);
|
||||
|
||||
// SaveGameInfo
|
||||
|
||||
py::class_<SaveGameInfo, PySaveGameInfo>(m, "SaveGameInfo")
|
||||
.def(py::init<>())
|
||||
.def("getMissingAssets", &SaveGameInfo::getMissingAssets, "save"_a)
|
||||
.def("getSaveGameWidget", &SaveGameInfo::getSaveGameWidget,
|
||||
py::return_value_policy::reference, "parent"_a, "[optional]");
|
||||
|
||||
// ScriptExtender
|
||||
|
||||
py::class_<ScriptExtender, PyScriptExtender>(m, "ScriptExtender")
|
||||
.def(py::init<>())
|
||||
.def("BinaryName", &ScriptExtender::BinaryName)
|
||||
.def("PluginPath", &ScriptExtender::PluginPath)
|
||||
.def("loaderName", &ScriptExtender::loaderName)
|
||||
.def("loaderPath", &ScriptExtender::loaderPath)
|
||||
.def("savegameExtension", &ScriptExtender::savegameExtension)
|
||||
.def("isInstalled", &ScriptExtender::isInstalled)
|
||||
.def("getExtenderVersion", &ScriptExtender::getExtenderVersion)
|
||||
.def("getArch", &ScriptExtender::getArch);
|
||||
|
||||
// UnmanagedMods
|
||||
|
||||
py::class_<UnmanagedMods, PyPyUnmanagedMods>(m, "UnmanagedMods")
|
||||
.def(py::init<>())
|
||||
.def("mods", &UnmanagedMods::mods, "official_only"_a)
|
||||
.def("displayName", &UnmanagedMods::displayName, "mod_name"_a)
|
||||
.def("referenceFile", &UnmanagedMods::referenceFile, "mod_name"_a)
|
||||
.def("secondaryFiles", &UnmanagedMods::secondaryFiles, "mod_name"_a);
|
||||
}
|
||||
|
||||
} // namespace mo2::python
|
||||
|
||||
namespace mo2::python {
|
||||
|
||||
class GameFeaturesHelper {
|
||||
using GameFeatures = std::tuple<BSAInvalidation, DataArchives, GamePlugins,
|
||||
LocalSavegames, ModDataChecker, ModDataContent,
|
||||
SaveGameInfo, ScriptExtender, UnmanagedMods>;
|
||||
|
||||
template <class F, std::size_t... Is>
|
||||
static void helper(F&& f, std::index_sequence<Is...>)
|
||||
{
|
||||
(f(static_cast<std::tuple_element_t<Is, GameFeatures>*>(nullptr)), ...);
|
||||
}
|
||||
|
||||
public:
|
||||
// apply the function f on a null-pointer of type Feature* for each game
|
||||
// feature
|
||||
template <class F>
|
||||
static void apply(F&& f)
|
||||
{
|
||||
helper(f, std::make_index_sequence<std::tuple_size_v<GameFeatures>>{});
|
||||
}
|
||||
};
|
||||
|
||||
pybind11::object extract_feature(IPluginGame const& game, pybind11::object type)
|
||||
{
|
||||
py::object py_feature = py::none();
|
||||
GameFeaturesHelper::apply([&]<class Feature>(Feature* feature) {
|
||||
if (py::type::of<Feature>().is(type)) {
|
||||
py_feature = py::cast(game.feature<Feature>(),
|
||||
py::return_value_policy::reference);
|
||||
}
|
||||
});
|
||||
return py_feature;
|
||||
}
|
||||
|
||||
pybind11::dict extract_feature_list(IPluginGame const& game)
|
||||
{
|
||||
// constructing a dict from class name to actual object
|
||||
py::dict dict;
|
||||
GameFeaturesHelper::apply([&]<class Feature>(Feature* feature) {
|
||||
dict[py::type::of<Feature>()] =
|
||||
py::cast(game.feature<Feature>(), py::return_value_policy::reference);
|
||||
});
|
||||
return dict;
|
||||
}
|
||||
|
||||
std::map<std::type_index, std::any>
|
||||
convert_feature_list(py::dict const& py_features)
|
||||
{
|
||||
std::map<std::type_index, std::any> features;
|
||||
GameFeaturesHelper::apply([&]<class Feature>(Feature* feature) {
|
||||
const auto py_type = py::type::of<Feature>();
|
||||
if (py_features.contains(py_type)) {
|
||||
features[std::type_index(typeid(Feature))] =
|
||||
py_features[py_type].cast<Feature*>();
|
||||
}
|
||||
});
|
||||
return features;
|
||||
}
|
||||
|
||||
} // namespace mo2::python
|
||||
@@ -0,0 +1,333 @@
|
||||
#include "pyfiletree.h"
|
||||
|
||||
#include <tuple>
|
||||
#include <variant>
|
||||
|
||||
#include "../pybind11_all.h"
|
||||
|
||||
#include <ifiletree.h>
|
||||
#include <log.h>
|
||||
|
||||
namespace py = pybind11;
|
||||
using namespace MOBase;
|
||||
|
||||
namespace mo2::detail {
|
||||
|
||||
// filetree implementation for testing purpose
|
||||
//
|
||||
class PyFileTree : public IFileTree {
|
||||
public:
|
||||
using callback_t = std::function<bool(QString, bool)>;
|
||||
|
||||
PyFileTree(std::shared_ptr<const IFileTree> parent, QString name,
|
||||
callback_t callback)
|
||||
: FileTreeEntry(parent, name), IFileTree(), m_Callback(callback)
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<FileTreeEntry> addFile(QString name, bool) override
|
||||
{
|
||||
if (m_Callback && !m_Callback(name, false)) {
|
||||
throw UnsupportedOperationException("File rejected by callback.");
|
||||
}
|
||||
return IFileTree::addFile(name);
|
||||
}
|
||||
|
||||
std::shared_ptr<IFileTree> addDirectory(QString name) override
|
||||
{
|
||||
if (m_Callback && !m_Callback(name, true)) {
|
||||
throw UnsupportedOperationException("Directory rejected by callback.");
|
||||
}
|
||||
return IFileTree::addDirectory(name);
|
||||
}
|
||||
|
||||
protected:
|
||||
std::shared_ptr<IFileTree>
|
||||
makeDirectory(std::shared_ptr<const IFileTree> parent,
|
||||
QString name) const override
|
||||
{
|
||||
return std::make_shared<PyFileTree>(parent, name, m_Callback);
|
||||
}
|
||||
|
||||
bool
|
||||
doPopulate(std::shared_ptr<const IFileTree> parent,
|
||||
std::vector<std::shared_ptr<FileTreeEntry>>& entries) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
std::shared_ptr<IFileTree> doClone() const override
|
||||
{
|
||||
return std::make_shared<PyFileTree>(nullptr, name(), m_Callback);
|
||||
}
|
||||
|
||||
private:
|
||||
callback_t m_Callback;
|
||||
};
|
||||
|
||||
} // namespace mo2::detail
|
||||
|
||||
#pragma optimize("", off)
|
||||
|
||||
namespace pybind11 {
|
||||
const void* polymorphic_type_hook<FileTreeEntry>::get(const FileTreeEntry* src,
|
||||
const std::type_info*& type)
|
||||
{
|
||||
if (auto p = dynamic_cast<const IFileTree*>(src)) {
|
||||
type = &typeid(IFileTree);
|
||||
return p;
|
||||
}
|
||||
return src;
|
||||
}
|
||||
} // namespace pybind11
|
||||
|
||||
namespace mo2::python {
|
||||
|
||||
void add_ifiletree_bindings(pybind11::module_& m)
|
||||
{
|
||||
|
||||
// FileTreeEntry Scope:
|
||||
auto fileTreeEntryClass =
|
||||
py::class_<FileTreeEntry, std::shared_ptr<FileTreeEntry>>(m,
|
||||
"FileTreeEntry");
|
||||
|
||||
// we do not use the enum directly, we will mostly bind the FileTypes
|
||||
// (with an S)
|
||||
py::enum_<FileTreeEntry::FileType>(fileTreeEntryClass, "FileType",
|
||||
py::arithmetic{});
|
||||
py::class_<FileTreeEntry::FileTypes>(fileTreeEntryClass, "FileTypes")
|
||||
.def_property_readonly_static("FILE",
|
||||
[](py::object) {
|
||||
return FileTreeEntry::FILE;
|
||||
})
|
||||
.def_property_readonly_static("DIRECTORY",
|
||||
[](py::object) {
|
||||
return FileTreeEntry::DIRECTORY;
|
||||
})
|
||||
.def_property_readonly_static("FILE_OR_DIRECTORY",
|
||||
[](py::object) {
|
||||
return FileTreeEntry::FILE_OR_DIRECTORY;
|
||||
})
|
||||
|
||||
.def(py::self == py::self)
|
||||
.def(py::self != py::self)
|
||||
.def(py::self | py::self);
|
||||
py::implicitly_convertible<FileTreeEntry::FileType, FileTreeEntry::FileTypes>();
|
||||
|
||||
fileTreeEntryClass
|
||||
.def_property_readonly_static("FILE",
|
||||
[](py::object) {
|
||||
return FileTreeEntry::FILE;
|
||||
})
|
||||
.def_property_readonly_static("DIRECTORY",
|
||||
[](py::object) {
|
||||
return FileTreeEntry::DIRECTORY;
|
||||
})
|
||||
.def_property_readonly_static("FILE_OR_DIRECTORY", [](py::object) {
|
||||
return FileTreeEntry::FILE_OR_DIRECTORY;
|
||||
});
|
||||
|
||||
fileTreeEntryClass
|
||||
|
||||
.def("isFile", &FileTreeEntry::isFile)
|
||||
.def("isDir", &FileTreeEntry::isDir)
|
||||
// Forcing the conversion to FileTypeS to avoid having to expose
|
||||
// FileType in python:
|
||||
.def("fileType", &FileTreeEntry::fileType)
|
||||
// This should probably not be exposed in python since we provide
|
||||
// automatic downcast: .def("getTree",
|
||||
// static_cast<std::shared_ptr<IFileTree>(FileTreeEntry::*)()>(&FileTreeEntry::astree))
|
||||
.def("name", &FileTreeEntry::name)
|
||||
.def("suffix", &FileTreeEntry::suffix)
|
||||
.def(
|
||||
"hasSuffix",
|
||||
[](FileTreeEntry* entry, QStringList suffixes) {
|
||||
return entry->hasSuffix(suffixes);
|
||||
},
|
||||
py::arg("suffixes"))
|
||||
.def(
|
||||
"hasSuffix",
|
||||
[](FileTreeEntry* entry, QString suffix) {
|
||||
return entry->hasSuffix(suffix);
|
||||
},
|
||||
py::arg("suffix"))
|
||||
.def("parent", py::overload_cast<>(&FileTreeEntry::parent), "[optional]")
|
||||
.def("path", &FileTreeEntry::path, py::arg("sep") = "\\")
|
||||
.def("pathFrom", &FileTreeEntry::pathFrom, py::arg("tree"),
|
||||
py::arg("sep") = "\\")
|
||||
|
||||
// Mutable operation:
|
||||
.def("detach", &FileTreeEntry::detach)
|
||||
.def("moveTo", &FileTreeEntry::moveTo, py::arg("tree"))
|
||||
|
||||
// Special methods:
|
||||
.def("__eq__",
|
||||
[](const FileTreeEntry* entry, QString other) {
|
||||
return entry->compare(other) == 0;
|
||||
})
|
||||
.def("__eq__",
|
||||
[](const FileTreeEntry* entry, std::shared_ptr<FileTreeEntry> other) {
|
||||
return entry == other.get();
|
||||
})
|
||||
|
||||
// Special methods for debug:
|
||||
.def("__repr__", [](const FileTreeEntry* entry) {
|
||||
return "FileTreeEntry(\"" + entry->name() + "\")";
|
||||
});
|
||||
|
||||
// IFileTree scope:
|
||||
auto iFileTreeClass =
|
||||
py::class_<IFileTree, FileTreeEntry, std::shared_ptr<IFileTree>>(
|
||||
m, "IFileTree", py::multiple_inheritance());
|
||||
|
||||
py::enum_<IFileTree::InsertPolicy>(iFileTreeClass, "InsertPolicy")
|
||||
.value("FAIL_IF_EXISTS", IFileTree::InsertPolicy::FAIL_IF_EXISTS)
|
||||
.value("REPLACE", IFileTree::InsertPolicy::REPLACE)
|
||||
.value("MERGE", IFileTree::InsertPolicy::MERGE)
|
||||
.export_values();
|
||||
|
||||
py::enum_<IFileTree::WalkReturn>(iFileTreeClass, "WalkReturn")
|
||||
.value("CONTINUE", IFileTree::WalkReturn::CONTINUE)
|
||||
.value("STOP", IFileTree::WalkReturn::STOP)
|
||||
.value("SKIP", IFileTree::WalkReturn::SKIP)
|
||||
.export_values();
|
||||
|
||||
// Non-mutable operations:
|
||||
iFileTreeClass.def("exists",
|
||||
py::overload_cast<QString, IFileTree::FileTypes>(
|
||||
&IFileTree::exists, py::const_),
|
||||
py::arg("path"),
|
||||
py::arg("type") = IFileTree::FILE_OR_DIRECTORY);
|
||||
iFileTreeClass.def(
|
||||
"find", py::overload_cast<QString, IFileTree::FileTypes>(&IFileTree::find),
|
||||
py::arg("path"), py::arg("type") = IFileTree::FILE_OR_DIRECTORY,
|
||||
"[optional]");
|
||||
iFileTreeClass.def("pathTo", &IFileTree::pathTo, py::arg("entry"),
|
||||
py::arg("sep") = "\\");
|
||||
|
||||
// Note: walk() would probably be better as a generator in python, but
|
||||
// it is likely impossible to construct from the C++ walk() method.
|
||||
iFileTreeClass.def("walk", &IFileTree::walk, py::arg("callback"),
|
||||
py::arg("sep") = "\\");
|
||||
|
||||
// Kind-of-static operations:
|
||||
iFileTreeClass.def("createOrphanTree", &IFileTree::createOrphanTree,
|
||||
py::arg("name") = "");
|
||||
|
||||
// addFile() and addDirectory throws exception instead of returning null
|
||||
// pointer in order to have better traces.
|
||||
iFileTreeClass.def(
|
||||
"addFile",
|
||||
[](IFileTree* w, QString path, bool replaceIfExists) {
|
||||
auto result = w->addFile(path, replaceIfExists);
|
||||
if (result == nullptr) {
|
||||
throw std::logic_error("addFile failed");
|
||||
}
|
||||
return result;
|
||||
},
|
||||
py::arg("path"), py::arg("replace_if_exists") = false);
|
||||
iFileTreeClass.def(
|
||||
"addDirectory",
|
||||
[](IFileTree* w, QString path) {
|
||||
auto result = w->addDirectory(path);
|
||||
if (result == nullptr) {
|
||||
throw std::logic_error("addDirectory failed");
|
||||
}
|
||||
return result;
|
||||
},
|
||||
py::arg("path"));
|
||||
|
||||
// Merge needs custom return types depending if the user wants overrides
|
||||
// or not. A failure is translated into an exception for easier tracing
|
||||
// and handling.
|
||||
iFileTreeClass.def(
|
||||
"merge",
|
||||
[](IFileTree* p, std::shared_ptr<IFileTree> other, bool returnOverwrites)
|
||||
-> std::variant<IFileTree::OverwritesType, std::size_t> {
|
||||
IFileTree::OverwritesType overwrites;
|
||||
auto result = p->merge(other, returnOverwrites ? &overwrites : nullptr);
|
||||
if (result == IFileTree::MERGE_FAILED) {
|
||||
throw std::logic_error("merge failed");
|
||||
}
|
||||
if (returnOverwrites) {
|
||||
return {overwrites};
|
||||
}
|
||||
return {result};
|
||||
},
|
||||
py::arg("other"), py::arg("overwrites") = false);
|
||||
|
||||
// Insert and erase returns an iterator, which makes no sense in python,
|
||||
// so we convert it to bool. Erase is also renamed "remove" since
|
||||
// "erase" is very C++.
|
||||
iFileTreeClass.def(
|
||||
"insert",
|
||||
[](IFileTree* p, std::shared_ptr<FileTreeEntry> entry,
|
||||
IFileTree::InsertPolicy insertPolicy) {
|
||||
return p->insert(entry, insertPolicy) == p->end();
|
||||
},
|
||||
py::arg("entry"),
|
||||
py::arg("policy") = IFileTree::InsertPolicy::FAIL_IF_EXISTS);
|
||||
|
||||
iFileTreeClass.def(
|
||||
"remove",
|
||||
[](IFileTree* p, QString name) {
|
||||
return p->erase(name).first != p->end();
|
||||
},
|
||||
py::arg("name"));
|
||||
iFileTreeClass.def(
|
||||
"remove",
|
||||
[](IFileTree* p, std::shared_ptr<FileTreeEntry> entry) {
|
||||
return p->erase(entry) != p->end();
|
||||
},
|
||||
py::arg("entry"));
|
||||
|
||||
iFileTreeClass.def("move", &IFileTree::move, py::arg("entry"), py::arg("path"),
|
||||
py::arg("policy") = IFileTree::InsertPolicy::FAIL_IF_EXISTS);
|
||||
iFileTreeClass.def(
|
||||
"copy",
|
||||
[](IFileTree* w, std::shared_ptr<FileTreeEntry> entry, QString path,
|
||||
IFileTree::InsertPolicy insertPolicy) {
|
||||
auto result = w->copy(entry, path, insertPolicy);
|
||||
if (result == nullptr) {
|
||||
throw std::logic_error("copy failed");
|
||||
}
|
||||
return result;
|
||||
},
|
||||
py::arg("entry"), py::arg("path") = "",
|
||||
py::arg("insert_policy") = IFileTree::InsertPolicy::FAIL_IF_EXISTS);
|
||||
|
||||
iFileTreeClass.def("clear", &IFileTree::clear);
|
||||
iFileTreeClass.def("removeAll", &IFileTree::removeAll, py::arg("names"));
|
||||
iFileTreeClass.def("removeIf", &IFileTree::removeIf, py::arg("filter"));
|
||||
|
||||
// Special methods:
|
||||
iFileTreeClass.def("__getitem__",
|
||||
py::overload_cast<std::size_t>(&IFileTree::at));
|
||||
|
||||
iFileTreeClass.def("__iter__", [](IFileTree* tree) {
|
||||
return py::make_iterator(*tree);
|
||||
});
|
||||
iFileTreeClass.def("__len__", &IFileTree::size);
|
||||
iFileTreeClass.def(
|
||||
"__bool__", +[](const IFileTree* tree) {
|
||||
return !tree->empty();
|
||||
});
|
||||
iFileTreeClass.def(
|
||||
"__repr__", +[](const IFileTree* entry) {
|
||||
return "IFileTree(\"" + entry->name() + "\")";
|
||||
});
|
||||
}
|
||||
|
||||
void add_make_tree_function(pybind11::module_& m)
|
||||
{
|
||||
m.def(
|
||||
"makeTree",
|
||||
[](mo2::detail::PyFileTree::callback_t callback)
|
||||
-> std::shared_ptr<IFileTree> {
|
||||
return std::make_shared<mo2::detail::PyFileTree>(nullptr, "", callback);
|
||||
},
|
||||
py::arg("callback") = mo2::detail::PyFileTree::callback_t{});
|
||||
}
|
||||
|
||||
} // namespace mo2::python
|
||||
|
||||
#pragma optimize("", on)
|
||||
@@ -0,0 +1,34 @@
|
||||
#ifndef MO2_PYTHON_FILETREE_H
|
||||
#define MO2_PYTHON_FILETREE_H
|
||||
|
||||
#include "../pybind11_all.h"
|
||||
|
||||
#include <ifiletree.h>
|
||||
|
||||
namespace pybind11 {
|
||||
template <>
|
||||
struct polymorphic_type_hook<MOBase::FileTreeEntry> {
|
||||
static const void* get(const MOBase::FileTreeEntry* src,
|
||||
const std::type_info*& type);
|
||||
};
|
||||
} // namespace pybind11
|
||||
|
||||
namespace mo2::python {
|
||||
|
||||
/**
|
||||
* @brief Add bindings for FileTreeEntry andIFileTree to the given module.
|
||||
*
|
||||
* @param mobase Module to add the bindings to.
|
||||
*/
|
||||
void add_ifiletree_bindings(pybind11::module_& m);
|
||||
|
||||
/**
|
||||
* @brief Add makeTree() function to the given module, useful for debugging.
|
||||
*
|
||||
* @param mobase Module to add the function to.
|
||||
*/
|
||||
void add_make_tree_function(pybind11::module_& m);
|
||||
|
||||
} // namespace mo2::python
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,272 @@
|
||||
#include "wrappers.h"
|
||||
|
||||
#include <tuple>
|
||||
|
||||
#include "pyplugins.h"
|
||||
|
||||
namespace py = pybind11;
|
||||
using namespace pybind11::literals;
|
||||
using namespace MOBase;
|
||||
|
||||
namespace mo2::python {
|
||||
|
||||
std::map<std::type_index, std::any> PyPluginGame::featureList() const
|
||||
{
|
||||
py::dict pyFeatures = [this]() {
|
||||
PYBIND11_OVERRIDE_PURE(py::dict, IPluginGame, _featureList, );
|
||||
}();
|
||||
|
||||
return convert_feature_list(pyFeatures);
|
||||
}
|
||||
|
||||
// this one is kind of big so it has its own function
|
||||
void add_iplugingame_bindings(pybind11::module_ m)
|
||||
{
|
||||
py::enum_<IPluginGame::LoadOrderMechanism>(m, "LoadOrderMechanism")
|
||||
.value("FileTime", IPluginGame::LoadOrderMechanism::FileTime)
|
||||
.value("PluginsTxt", IPluginGame::LoadOrderMechanism::PluginsTxt)
|
||||
|
||||
.value("FILE_TIME", IPluginGame::LoadOrderMechanism::FileTime)
|
||||
.value("PLUGINS_TXT", IPluginGame::LoadOrderMechanism::PluginsTxt);
|
||||
|
||||
py::enum_<IPluginGame::SortMechanism>(m, "SortMechanism")
|
||||
.value("NONE", IPluginGame::SortMechanism::NONE)
|
||||
.value("MLOX", IPluginGame::SortMechanism::MLOX)
|
||||
.value("BOSS", IPluginGame::SortMechanism::BOSS)
|
||||
.value("LOOT", IPluginGame::SortMechanism::LOOT);
|
||||
|
||||
// this does not actually do the conversion, but might be convenient
|
||||
// for accessing the names for enum bits
|
||||
py::enum_<IPluginGame::ProfileSetting>(m, "ProfileSetting", py::arithmetic())
|
||||
.value("mods", IPluginGame::MODS)
|
||||
.value("configuration", IPluginGame::CONFIGURATION)
|
||||
.value("savegames", IPluginGame::SAVEGAMES)
|
||||
.value("preferDefaults", IPluginGame::PREFER_DEFAULTS)
|
||||
|
||||
.value("MODS", IPluginGame::MODS)
|
||||
.value("CONFIGURATION", IPluginGame::CONFIGURATION)
|
||||
.value("SAVEGAMES", IPluginGame::SAVEGAMES)
|
||||
.value("PREFER_DEFAULTS", IPluginGame::PREFER_DEFAULTS);
|
||||
|
||||
py::class_<IPluginGame, PyPluginGame, IPlugin,
|
||||
std::unique_ptr<IPluginGame, py::nodelete>>(
|
||||
m, "IPluginGame", py::multiple_inheritance())
|
||||
.def(py::init<>())
|
||||
|
||||
.def("featureList", &extract_feature_list)
|
||||
.def("feature", &extract_feature, "feature_type"_a,
|
||||
py::return_value_policy::reference)
|
||||
|
||||
.def("detectGame", &IPluginGame::detectGame)
|
||||
.def("gameName", &IPluginGame::gameName)
|
||||
.def("initializeProfile", &IPluginGame::initializeProfile, "directory"_a,
|
||||
"settings"_a)
|
||||
.def("listSaves", &IPluginGame::listSaves, "folder"_a)
|
||||
.def("isInstalled", &IPluginGame::isInstalled)
|
||||
.def("gameIcon", &IPluginGame::gameIcon)
|
||||
.def("gameDirectory", &IPluginGame::gameDirectory)
|
||||
.def("dataDirectory", &IPluginGame::dataDirectory)
|
||||
.def("setGamePath", &IPluginGame::setGamePath, "path"_a)
|
||||
.def("documentsDirectory", &IPluginGame::documentsDirectory)
|
||||
.def("savesDirectory", &IPluginGame::savesDirectory)
|
||||
.def("executables", &IPluginGame::executables)
|
||||
.def("executableForcedLoads", &IPluginGame::executableForcedLoads)
|
||||
.def("steamAPPId", &IPluginGame::steamAPPId)
|
||||
.def("primaryPlugins", &IPluginGame::primaryPlugins)
|
||||
.def("gameVariants", &IPluginGame::gameVariants)
|
||||
.def("setGameVariant", &IPluginGame::setGameVariant, "variant"_a)
|
||||
.def("binaryName", &IPluginGame::binaryName)
|
||||
.def("gameShortName", &IPluginGame::gameShortName)
|
||||
.def("primarySources", &IPluginGame::primarySources)
|
||||
.def("validShortNames", &IPluginGame::validShortNames)
|
||||
.def("gameNexusName", &IPluginGame::gameNexusName)
|
||||
.def("iniFiles", &IPluginGame::iniFiles)
|
||||
.def("DLCPlugins", &IPluginGame::DLCPlugins)
|
||||
.def("CCPlugins", &IPluginGame::CCPlugins)
|
||||
.def("loadOrderMechanism", &IPluginGame::loadOrderMechanism)
|
||||
.def("sortMechanism", &IPluginGame::sortMechanism)
|
||||
.def("nexusModOrganizerID", &IPluginGame::nexusModOrganizerID)
|
||||
.def("nexusGameID", &IPluginGame::nexusGameID)
|
||||
.def("looksValid", &IPluginGame::looksValid, "directory"_a)
|
||||
.def("gameVersion", &IPluginGame::gameVersion)
|
||||
.def("getLauncherName", &IPluginGame::getLauncherName)
|
||||
.def("getSupportURL", &IPluginGame::getSupportURL);
|
||||
}
|
||||
|
||||
// multiple installers
|
||||
void add_iplugininstaller_bindings(pybind11::module_ m)
|
||||
{
|
||||
py::enum_<IPluginInstaller::EInstallResult>(m, "InstallResult")
|
||||
.value("SUCCESS", IPluginInstaller::RESULT_SUCCESS)
|
||||
.value("FAILED", IPluginInstaller::RESULT_FAILED)
|
||||
.value("CANCELED", IPluginInstaller::RESULT_CANCELED)
|
||||
.value("MANUAL_REQUESTED", IPluginInstaller::RESULT_MANUALREQUESTED)
|
||||
.value("NOT_ATTEMPTED", IPluginInstaller::RESULT_NOTATTEMPTED);
|
||||
|
||||
// this is bind but should not be inherited in Python - does not make sense,
|
||||
// having it makes it simpler to bind the Simple and Custom installers
|
||||
py::class_<IPluginInstaller, PyPluginInstallerBase<IPluginInstaller>, IPlugin,
|
||||
std::unique_ptr<IPluginInstaller, py::nodelete>>(
|
||||
m, "IPluginInstaller", py::multiple_inheritance())
|
||||
.def("isArchiveSupported", &IPluginInstaller::isArchiveSupported, "tree"_a)
|
||||
.def("priority", &IPluginInstaller::priority)
|
||||
.def("onInstallationStart", &IPluginInstaller::onInstallationStart,
|
||||
"archive"_a, "reinstallation"_a, "current_mod"_a)
|
||||
.def("onInstallationEnd", &IPluginInstaller::onInstallationEnd, "result"_a,
|
||||
"new_mod"_a)
|
||||
.def("isManualInstaller", &IPluginInstaller::isManualInstaller)
|
||||
.def("setParentWidget", &IPluginInstaller::setParentWidget, "parent"_a)
|
||||
.def("setInstallationManager", &IPluginInstaller::setInstallationManager,
|
||||
"manager"_a)
|
||||
.def("_parentWidget",
|
||||
&PyPluginInstallerBase<IPluginInstaller>::parentWidget)
|
||||
.def("_manager", &PyPluginInstallerBase<IPluginInstaller>::manager,
|
||||
py::return_value_policy::reference);
|
||||
|
||||
py::class_<IPluginInstallerSimple, PyPluginInstallerSimple, IPluginInstaller,
|
||||
std::unique_ptr<IPluginInstallerSimple, py::nodelete>>(
|
||||
m, "IPluginInstallerSimple", py::multiple_inheritance())
|
||||
.def(py::init<>())
|
||||
|
||||
// note: keeping the variant here even if we always return a tuple
|
||||
// to be consistent with the wrapper and have proper stubs generation.
|
||||
.def(
|
||||
"install",
|
||||
[](IPluginInstallerSimple* p, GuessedValue<QString>& modName,
|
||||
std::shared_ptr<IFileTree>& tree, QString& version,
|
||||
int& nexusID) -> PyPluginInstallerSimple::py_install_return_type {
|
||||
auto result = p->install(modName, tree, version, nexusID);
|
||||
return std::make_tuple(result, tree, version, nexusID);
|
||||
},
|
||||
"name"_a, "tree"_a, "version"_a, "nexus_id"_a);
|
||||
|
||||
py::class_<IPluginInstallerCustom, PyPluginInstallerCustom, IPluginInstaller,
|
||||
std::unique_ptr<IPluginInstallerCustom, py::nodelete>>(
|
||||
m, "IPluginInstallerCustom", py::multiple_inheritance())
|
||||
.def(py::init<>())
|
||||
.def("isArchiveSupported", &IPluginInstallerCustom::isArchiveSupported,
|
||||
"archive_name"_a)
|
||||
.def("supportedExtensions", &IPluginInstallerCustom::supportedExtensions)
|
||||
.def("install", &IPluginInstallerCustom::install, "mod_name"_a,
|
||||
"game_name"_a, "archive_name"_a, "version"_a, "nexus_id"_a);
|
||||
}
|
||||
|
||||
void add_plugins_bindings(pybind11::module_ m)
|
||||
{
|
||||
py::class_<IPlugin, PyPlugin, std::unique_ptr<IPlugin, py::nodelete>>(
|
||||
m, "IPluginBase", py::multiple_inheritance())
|
||||
.def(py::init<>())
|
||||
.def("init", &IPlugin::init, "organizer"_a)
|
||||
.def("name", &IPlugin::name)
|
||||
.def("localizedName", &IPlugin::localizedName)
|
||||
.def("master", &IPlugin::master)
|
||||
.def("author", &IPlugin::author)
|
||||
.def("description", &IPlugin::description)
|
||||
.def("version", &IPlugin::version)
|
||||
.def("requirements", &IPlugin::requirements)
|
||||
.def("settings", &IPlugin::settings)
|
||||
.def("enabledByDefault", &IPlugin::enabledByDefault);
|
||||
|
||||
py::class_<IPyPlugin, PyPlugin, IPlugin,
|
||||
std::unique_ptr<IPyPlugin, py::nodelete>>(m, "IPlugin",
|
||||
py::multiple_inheritance())
|
||||
.def(py::init<>());
|
||||
|
||||
py::class_<IPyPluginFileMapper, PyPluginFileMapper, IPlugin,
|
||||
std::unique_ptr<IPyPluginFileMapper, py::nodelete>>(
|
||||
m, "IPluginFileMapper", py::multiple_inheritance())
|
||||
.def(py::init<>())
|
||||
.def("mappings", &IPluginFileMapper::mappings);
|
||||
|
||||
py::class_<IPyPluginDiagnose, PyPluginDiagnose, IPlugin,
|
||||
std::unique_ptr<IPyPluginDiagnose, py::nodelete>>(
|
||||
m, "IPluginDiagnose", py::multiple_inheritance())
|
||||
.def(py::init<>())
|
||||
.def("activeProblems", &IPluginDiagnose::activeProblems)
|
||||
.def("shortDescription", &IPluginDiagnose::shortDescription, "key"_a)
|
||||
.def("fullDescription", &IPluginDiagnose::fullDescription, "key"_a)
|
||||
.def("hasGuidedFix", &IPluginDiagnose::hasGuidedFix, "key"_a)
|
||||
.def("startGuidedFix", &IPluginDiagnose::startGuidedFix, "key"_a)
|
||||
.def("_invalidate", &PyPluginDiagnose::invalidate);
|
||||
|
||||
py::class_<IPluginTool, PyPluginTool, IPlugin,
|
||||
std::unique_ptr<IPluginTool, py::nodelete>>(
|
||||
m, "IPluginTool", py::multiple_inheritance())
|
||||
.def(py::init<>())
|
||||
.def("displayName", &IPluginTool::displayName)
|
||||
.def("tooltip", &IPluginTool::tooltip)
|
||||
.def("icon", &IPluginTool::icon)
|
||||
.def("display", &IPluginTool::display)
|
||||
.def("setParentWidget", &IPluginTool::setParentWidget)
|
||||
.def("_parentWidget", &PyPluginTool::parentWidget);
|
||||
|
||||
py::class_<IPluginPreview, PyPluginPreview, IPlugin,
|
||||
std::unique_ptr<IPluginPreview, py::nodelete>>(
|
||||
m, "IPluginPreview", py::multiple_inheritance())
|
||||
.def(py::init<>())
|
||||
.def("supportedExtensions", &IPluginPreview::supportedExtensions)
|
||||
.def("genFilePreview", &IPluginPreview::genFilePreview, "filename"_a,
|
||||
"max_size"_a);
|
||||
|
||||
py::class_<IPluginModPage, PyPluginModPage, IPluginModPage,
|
||||
std::unique_ptr<IPluginModPage, py::nodelete>>(
|
||||
m, "IPluginModPage", py::multiple_inheritance())
|
||||
.def(py::init<>())
|
||||
.def("displayName", &IPluginModPage::displayName)
|
||||
.def("icon", &IPluginModPage::icon)
|
||||
.def("pageURL", &IPluginModPage::pageURL)
|
||||
.def("useIntegratedBrowser", &IPluginModPage::useIntegratedBrowser)
|
||||
.def("handlesDownload", &IPluginModPage::handlesDownload, "page_url"_a,
|
||||
"download_url"_a, "fileinfo"_a)
|
||||
.def("setParentWidget", &IPluginModPage::setParentWidget, "parent"_a)
|
||||
.def("_parentWidget", &PyPluginModPage::parentWidget);
|
||||
|
||||
add_iplugingame_bindings(m);
|
||||
add_iplugininstaller_bindings(m);
|
||||
}
|
||||
|
||||
struct extract_plugins_helper {
|
||||
QList<QObject*> objects;
|
||||
|
||||
template <class IPluginClass>
|
||||
void append_if_instance(pybind11::object plugin_obj)
|
||||
{
|
||||
if (py::isinstance<IPluginClass>(plugin_obj)) {
|
||||
objects.append(plugin_obj.cast<IPluginClass*>());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
QList<QObject*> extract_plugins(pybind11::object plugin_obj)
|
||||
{
|
||||
extract_plugins_helper helper;
|
||||
|
||||
// we need to check the trampoline class for these since the interfaces do not
|
||||
// extend IPlugin
|
||||
helper.append_if_instance<IPyPluginFileMapper>(plugin_obj);
|
||||
helper.append_if_instance<IPyPluginDiagnose>(plugin_obj);
|
||||
|
||||
helper.append_if_instance<IPluginModPage>(plugin_obj);
|
||||
helper.append_if_instance<IPluginPreview>(plugin_obj);
|
||||
helper.append_if_instance<IPluginTool>(plugin_obj);
|
||||
|
||||
helper.append_if_instance<IPluginGame>(plugin_obj);
|
||||
|
||||
// we need to check the two installer types because IPluginInstaller does not
|
||||
// inherit QObject, and the trampoline do not have a common ancestor
|
||||
helper.append_if_instance<IPluginInstallerSimple>(plugin_obj);
|
||||
helper.append_if_instance<IPluginInstallerCustom>(plugin_obj);
|
||||
|
||||
if (helper.objects.isEmpty()) {
|
||||
helper.append_if_instance<IPyPlugin>(plugin_obj);
|
||||
}
|
||||
|
||||
// tie the lifetime of the Python object to the lifetime of the QObject
|
||||
for (auto* object : helper.objects) {
|
||||
py::qt::set_qt_owner(object, plugin_obj);
|
||||
}
|
||||
|
||||
return helper.objects;
|
||||
}
|
||||
|
||||
} // namespace mo2::python
|
||||
@@ -0,0 +1,493 @@
|
||||
#ifndef PYTHON_WRAPPERS_PYPLUGINS_H
|
||||
#define PYTHON_WRAPPERS_PYPLUGINS_H
|
||||
|
||||
#include "../pybind11_all.h"
|
||||
|
||||
#include <iinstallationmanager.h>
|
||||
|
||||
#include <iplugin.h>
|
||||
#include <iplugindiagnose.h>
|
||||
#include <ipluginfilemapper.h>
|
||||
#include <iplugingame.h>
|
||||
#include <iplugininstaller.h>
|
||||
#include <iplugininstallercustom.h>
|
||||
#include <iplugininstallersimple.h>
|
||||
#include <ipluginmodpage.h>
|
||||
#include <ipluginpreview.h>
|
||||
#include <iplugintool.h>
|
||||
|
||||
// these needs to be defined in a header file for automoc - this file is included only
|
||||
// in pyplugins.cpp
|
||||
namespace mo2::python {
|
||||
|
||||
using namespace MOBase;
|
||||
|
||||
// we need two base trampoline because IPluginGame has some final methods.
|
||||
template <class PluginBase>
|
||||
class PyPluginBaseNoFinal : public PluginBase {
|
||||
public:
|
||||
using PluginBase::PluginBase;
|
||||
|
||||
~PyPluginBaseNoFinal()
|
||||
{
|
||||
std::cout << "~PyPluginBaseNoFinal() " << typeid(this).name() << std::endl;
|
||||
}
|
||||
|
||||
bool init(IOrganizer* organizer) override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(bool, PluginBase, init, organizer);
|
||||
}
|
||||
QString name() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QString, PluginBase, name, );
|
||||
}
|
||||
QString localizedName() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE(QString, PluginBase, localizedName, );
|
||||
}
|
||||
QString master() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE(QString, PluginBase, master, );
|
||||
}
|
||||
QString author() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QString, PluginBase, author, );
|
||||
}
|
||||
QString description() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QString, PluginBase, description, );
|
||||
}
|
||||
VersionInfo version() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(VersionInfo, PluginBase, version, );
|
||||
}
|
||||
QList<PluginSetting> settings() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QList<PluginSetting>, PluginBase, settings, );
|
||||
}
|
||||
};
|
||||
|
||||
template <class PluginBase>
|
||||
class PyPluginBase : public PyPluginBaseNoFinal<PluginBase> {
|
||||
public:
|
||||
using PyPluginBaseNoFinal<PluginBase>::PyPluginBaseNoFinal;
|
||||
|
||||
std::vector<std::shared_ptr<const IPluginRequirement>> requirements() const
|
||||
{
|
||||
PYBIND11_OVERRIDE(std::vector<std::shared_ptr<const IPluginRequirement>>,
|
||||
PluginBase, requirements, );
|
||||
}
|
||||
bool enabledByDefault() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE(bool, PluginBase, enabledByDefault, );
|
||||
}
|
||||
};
|
||||
|
||||
// these classes do not inherit IPlugin or QObject so we need intermediate class to
|
||||
// get proper bindings
|
||||
class IPyPlugin : public QObject, public IPlugin {};
|
||||
class IPyPluginFileMapper : public IPyPlugin, public IPluginFileMapper {};
|
||||
class IPyPluginDiagnose : public IPyPlugin, public IPluginDiagnose {};
|
||||
|
||||
// PyXXX classes - trampoline classes for the plugins
|
||||
|
||||
class PyPlugin : public PyPluginBase<IPyPlugin> {
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(MOBase::IPlugin)
|
||||
};
|
||||
|
||||
class PyPluginFileMapper : public PyPluginBase<IPyPluginFileMapper> {
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(MOBase::IPlugin MOBase::IPluginFileMapper)
|
||||
public:
|
||||
MappingType mappings() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(MappingType, IPluginFileMapper, mappings, );
|
||||
}
|
||||
};
|
||||
|
||||
class PyPluginDiagnose : public PyPluginBase<IPyPluginDiagnose> {
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(MOBase::IPlugin MOBase::IPluginDiagnose)
|
||||
public:
|
||||
std::vector<unsigned int> activeProblems() const
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(std::vector<unsigned int>, IPluginDiagnose,
|
||||
activeProblems, );
|
||||
}
|
||||
|
||||
QString shortDescription(unsigned int key) const
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QString, IPluginDiagnose, shortDescription, key);
|
||||
}
|
||||
|
||||
QString fullDescription(unsigned int key) const
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QString, IPluginDiagnose, fullDescription, key);
|
||||
}
|
||||
|
||||
bool hasGuidedFix(unsigned int key) const
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(bool, IPluginDiagnose, hasGuidedFix, key);
|
||||
}
|
||||
|
||||
void startGuidedFix(unsigned int key) const
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(void, IPluginDiagnose, startGuidedFix, key);
|
||||
}
|
||||
|
||||
// we need to bring this in public scope
|
||||
using IPluginDiagnose::invalidate;
|
||||
};
|
||||
|
||||
class PyPluginTool : public PyPluginBase<IPluginTool> {
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(MOBase::IPlugin MOBase::IPluginTool)
|
||||
public:
|
||||
QString displayName() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QString, IPluginTool, displayName, );
|
||||
}
|
||||
QString tooltip() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QString, IPluginTool, tooltip, );
|
||||
}
|
||||
QIcon icon() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QIcon, IPluginTool, icon, );
|
||||
}
|
||||
void setParentWidget(QWidget* widget) override
|
||||
{
|
||||
PYBIND11_OVERRIDE(void, IPluginTool, setParentWidget, widget);
|
||||
}
|
||||
void display() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(void, IPluginTool, display, );
|
||||
}
|
||||
|
||||
// we need to bring this in public scope
|
||||
using IPluginTool::parentWidget;
|
||||
};
|
||||
|
||||
class PyPluginPreview : public PyPluginBase<IPluginPreview> {
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(MOBase::IPlugin MOBase::IPluginPreview)
|
||||
public:
|
||||
std::set<QString> supportedExtensions() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(std::set<QString>, IPluginPreview,
|
||||
supportedExtensions, );
|
||||
}
|
||||
|
||||
QWidget* genFilePreview(const QString& fileName,
|
||||
const QSize& maxSize) const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QWidget*, IPluginPreview, genFilePreview, fileName,
|
||||
maxSize);
|
||||
}
|
||||
};
|
||||
|
||||
class PyPluginModPage : public PyPluginBase<IPluginModPage> {
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(MOBase::IPlugin MOBase::IPluginModPage)
|
||||
public:
|
||||
QString displayName() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QString, IPluginModPage, displayName, );
|
||||
}
|
||||
|
||||
QIcon icon() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QIcon, IPluginModPage, icon, );
|
||||
}
|
||||
|
||||
QUrl pageURL() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QUrl, IPluginModPage, pageURL, );
|
||||
}
|
||||
|
||||
bool useIntegratedBrowser() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(bool, IPluginModPage, useIntegratedBrowser, );
|
||||
}
|
||||
|
||||
bool handlesDownload(const QUrl& pageURL, const QUrl& downloadURL,
|
||||
ModRepositoryFileInfo& fileInfo) const override
|
||||
{
|
||||
// TODO: cannot modify fileInfo from Python
|
||||
PYBIND11_OVERRIDE_PURE(bool, IPluginModPage, handlesDownload, pageURL,
|
||||
downloadURL, &fileInfo);
|
||||
}
|
||||
|
||||
void setParentWidget(QWidget* widget) override
|
||||
{
|
||||
PYBIND11_OVERRIDE(void, IPluginModPage, setParentWidget, widget);
|
||||
}
|
||||
|
||||
// we need to bring this in public scope
|
||||
using IPluginModPage::parentWidget;
|
||||
};
|
||||
|
||||
// installers
|
||||
template <class PluginInstallerBase>
|
||||
class PyPluginInstallerBase : public PyPluginBase<PluginInstallerBase> {
|
||||
public:
|
||||
using PyPluginBase<PluginInstallerBase>::PyPluginBase;
|
||||
|
||||
unsigned int priority() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(unsigned int, PluginInstallerBase, priority);
|
||||
}
|
||||
|
||||
bool isManualInstaller() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(bool, PluginInstallerBase, isManualInstaller, );
|
||||
}
|
||||
|
||||
void onInstallationStart(QString const& archive, bool reinstallation,
|
||||
IModInterface* currentMod)
|
||||
{
|
||||
PYBIND11_OVERRIDE(void, PluginInstallerBase, onInstallationStart, archive,
|
||||
reinstallation, currentMod);
|
||||
}
|
||||
|
||||
void onInstallationEnd(IPluginInstaller::EInstallResult result,
|
||||
IModInterface* newMod)
|
||||
{
|
||||
PYBIND11_OVERRIDE(void, PluginInstallerBase, onInstallationEnd, result,
|
||||
newMod);
|
||||
}
|
||||
|
||||
bool isArchiveSupported(std::shared_ptr<const IFileTree> tree) const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(bool, PluginInstallerBase, isArchiveSupported, tree);
|
||||
}
|
||||
|
||||
// we need to bring these in public scope
|
||||
using PluginInstallerBase::manager;
|
||||
using PluginInstallerBase::parentWidget;
|
||||
};
|
||||
|
||||
class PyPluginInstallerCustom
|
||||
: public PyPluginInstallerBase<IPluginInstallerCustom> {
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(
|
||||
MOBase::IPlugin MOBase::IPluginInstaller MOBase::IPluginInstallerCustom)
|
||||
public:
|
||||
bool isArchiveSupported(const QString& archiveName) const
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(bool, IPluginInstallerCustom, isArchiveSupported,
|
||||
archiveName);
|
||||
}
|
||||
|
||||
std::set<QString> supportedExtensions() const
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(std::set<QString>, IPluginInstallerCustom,
|
||||
supportedExtensions, );
|
||||
}
|
||||
|
||||
EInstallResult install(GuessedValue<QString>& modName, QString gameName,
|
||||
const QString& archiveName, const QString& version,
|
||||
int nexusID) override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(EInstallResult, IPluginInstallerCustom, install,
|
||||
&modName, gameName, archiveName, version, nexusID);
|
||||
}
|
||||
};
|
||||
|
||||
class PyPluginInstallerSimple
|
||||
: public PyPluginInstallerBase<IPluginInstallerSimple> {
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(
|
||||
MOBase::IPlugin MOBase::IPluginInstaller MOBase::IPluginInstallerSimple)
|
||||
public:
|
||||
using py_install_return_type =
|
||||
std::variant<IPluginInstaller::EInstallResult, std::shared_ptr<IFileTree>,
|
||||
std::tuple<IPluginInstaller::EInstallResult,
|
||||
std::shared_ptr<IFileTree>, QString, int>>;
|
||||
|
||||
EInstallResult install(GuessedValue<QString>& modName,
|
||||
std::shared_ptr<IFileTree>& tree, QString& version,
|
||||
int& nexusID) override
|
||||
{
|
||||
const auto result = [&, this]() {
|
||||
PYBIND11_OVERRIDE_PURE(py_install_return_type, IPluginInstallerSimple,
|
||||
install, &modName, tree, version, nexusID);
|
||||
}();
|
||||
|
||||
return std::visit(
|
||||
[&tree, &version, &nexusID](auto const& t) {
|
||||
using type = std::decay_t<decltype(t)>;
|
||||
if constexpr (std::is_same_v<type, EInstallResult>) {
|
||||
return t;
|
||||
}
|
||||
else if constexpr (std::is_same_v<type,
|
||||
std::shared_ptr<IFileTree>>) {
|
||||
tree = t;
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
else if constexpr (std::is_same_v<
|
||||
type, std::tuple<EInstallResult,
|
||||
std::shared_ptr<IFileTree>,
|
||||
QString, int>>) {
|
||||
tree = std::get<1>(t);
|
||||
version = std::get<2>(t);
|
||||
nexusID = std::get<3>(t);
|
||||
return std::get<0>(t);
|
||||
}
|
||||
},
|
||||
result);
|
||||
}
|
||||
};
|
||||
|
||||
// game
|
||||
class PyPluginGame : public PyPluginBaseNoFinal<IPluginGame> {
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(MOBase::IPlugin MOBase::IPluginGame)
|
||||
public:
|
||||
void detectGame() override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(void, IPluginGame, detectGame, );
|
||||
}
|
||||
QString gameName() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QString, IPluginGame, gameName, );
|
||||
}
|
||||
void initializeProfile(const QDir& directory,
|
||||
ProfileSettings settings) const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(void, IPluginGame, initializeProfile, directory,
|
||||
settings);
|
||||
}
|
||||
std::vector<std::shared_ptr<const ISaveGame>>
|
||||
listSaves(QDir folder) const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(std::vector<std::shared_ptr<const ISaveGame>>,
|
||||
IPluginGame, listSaves, folder);
|
||||
}
|
||||
bool isInstalled() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(bool, IPluginGame, isInstalled, );
|
||||
}
|
||||
QIcon gameIcon() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QIcon, IPluginGame, gameIcon, );
|
||||
}
|
||||
QDir gameDirectory() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QDir, IPluginGame, gameDirectory, );
|
||||
}
|
||||
QDir dataDirectory() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QDir, IPluginGame, dataDirectory, );
|
||||
}
|
||||
void setGamePath(const QString& path) override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(void, IPluginGame, setGamePath, path);
|
||||
}
|
||||
QDir documentsDirectory() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QDir, IPluginGame, documentsDirectory, );
|
||||
}
|
||||
QDir savesDirectory() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QDir, IPluginGame, savesDirectory, );
|
||||
}
|
||||
QList<ExecutableInfo> executables() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QList<ExecutableInfo>, IPluginGame, executables, );
|
||||
}
|
||||
QList<ExecutableForcedLoadSetting> executableForcedLoads() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QList<ExecutableForcedLoadSetting>, IPluginGame,
|
||||
executableForcedLoads, );
|
||||
}
|
||||
QString steamAPPId() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QString, IPluginGame, steamAPPId, );
|
||||
}
|
||||
QStringList primaryPlugins() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QStringList, IPluginGame, primaryPlugins, );
|
||||
}
|
||||
QStringList gameVariants() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QStringList, IPluginGame, gameVariants, );
|
||||
}
|
||||
void setGameVariant(const QString& variant) override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(void, IPluginGame, setGameVariant, variant);
|
||||
}
|
||||
QString binaryName() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QString, IPluginGame, binaryName, );
|
||||
}
|
||||
QString gameShortName() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QString, IPluginGame, gameShortName, );
|
||||
}
|
||||
QStringList primarySources() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QStringList, IPluginGame, primarySources, );
|
||||
}
|
||||
QStringList validShortNames() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QStringList, IPluginGame, validShortNames, );
|
||||
}
|
||||
QString gameNexusName() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QString, IPluginGame, gameNexusName, );
|
||||
}
|
||||
QStringList iniFiles() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QStringList, IPluginGame, iniFiles, );
|
||||
}
|
||||
QStringList DLCPlugins() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QStringList, IPluginGame, DLCPlugins, );
|
||||
}
|
||||
QStringList CCPlugins() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QStringList, IPluginGame, CCPlugins, );
|
||||
}
|
||||
LoadOrderMechanism loadOrderMechanism() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(LoadOrderMechanism, IPluginGame,
|
||||
loadOrderMechanism, );
|
||||
}
|
||||
SortMechanism sortMechanism() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(SortMechanism, IPluginGame, sortMechanism, );
|
||||
}
|
||||
int nexusModOrganizerID() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(int, IPluginGame, nexusModOrganizerID, );
|
||||
}
|
||||
int nexusGameID() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(int, IPluginGame, nexusGameID, );
|
||||
}
|
||||
bool looksValid(QDir const& dir) const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(bool, IPluginGame, looksValid, dir);
|
||||
}
|
||||
QString gameVersion() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QString, IPluginGame, gameVersion, );
|
||||
}
|
||||
QString getLauncherName() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QString, IPluginGame, getLauncherName, );
|
||||
}
|
||||
QString getSupportURL() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE(QString, IPluginGame, getSupportURL, );
|
||||
}
|
||||
|
||||
protected:
|
||||
std::map<std::type_index, std::any> featureList() const override;
|
||||
};
|
||||
|
||||
} // namespace mo2::python
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,74 @@
|
||||
#include "wrappers.h"
|
||||
|
||||
#include "../pybind11_all.h"
|
||||
|
||||
#include <report.h>
|
||||
|
||||
namespace py = pybind11;
|
||||
using namespace MOBase;
|
||||
|
||||
namespace mo2::python {
|
||||
|
||||
void add_widget_bindings(pybind11::module_ m)
|
||||
{
|
||||
// TaskDialog is also in Windows System.
|
||||
using TaskDialog = MOBase::TaskDialog;
|
||||
|
||||
// TaskDialog
|
||||
py::class_<TaskDialogButton>(m, "TaskDialogButton")
|
||||
.def(py::init<QString, QString, QMessageBox::StandardButton>(),
|
||||
py::arg("text"), py::arg("description"), py::arg("button"))
|
||||
.def(py::init<QString, QMessageBox::StandardButton>(), py::arg("text"),
|
||||
py::arg("button"));
|
||||
|
||||
py::class_<TaskDialog>(m, "TaskDialog")
|
||||
.def(py::init([](QWidget* parent, QString const& title, QString const& main,
|
||||
QString const& content, QString const& details,
|
||||
QMessageBox::Icon icon,
|
||||
std::vector<TaskDialogButton> const& buttons,
|
||||
std::variant<QString, std::tuple<QString, QString>> const&
|
||||
remember) {
|
||||
auto* dialog = new TaskDialog(parent, title);
|
||||
dialog->main(main).content(content).details(details).icon(icon);
|
||||
|
||||
for (auto& button : buttons) {
|
||||
dialog->button(button);
|
||||
}
|
||||
|
||||
std::visit(
|
||||
[dialog](auto const& item) {
|
||||
QString action, file;
|
||||
if constexpr (std::is_same_v<std::decay_t<decltype(item)>,
|
||||
QString>) {
|
||||
action = item;
|
||||
}
|
||||
else {
|
||||
action = std::get<0>(item);
|
||||
file = std::get<1>(item);
|
||||
}
|
||||
dialog->remember(action, file);
|
||||
},
|
||||
remember);
|
||||
|
||||
return dialog;
|
||||
}),
|
||||
py::return_value_policy::take_ownership,
|
||||
py::arg("parent") = static_cast<QWidget*>(nullptr),
|
||||
py::arg("title") = "", py::arg("main") = "", py::arg("content") = "",
|
||||
py::arg("details") = "", py::arg("icon") = QMessageBox::NoIcon,
|
||||
py::arg("buttons") = std::vector<TaskDialogButton>{},
|
||||
py::arg("remember") = "")
|
||||
.def("setTitle", &TaskDialog::title, py::arg("title"))
|
||||
.def("setMain", &TaskDialog::main, py::arg("main"))
|
||||
.def("setContent", &TaskDialog::content, py::arg("content"))
|
||||
.def("setDetails", &TaskDialog::details, py::arg("details"))
|
||||
.def("setIcon", &TaskDialog::icon, py::arg("icon"))
|
||||
.def("addButton", &TaskDialog::button, py::arg("button"))
|
||||
.def("setRemember", &TaskDialog::remember, py::arg("action"),
|
||||
py::arg("file") = "")
|
||||
.def("setWidth", &TaskDialog::setWidth, py::arg("widget"))
|
||||
.def("addContent", &TaskDialog::addContent, py::arg("widget"))
|
||||
.def("exec", &TaskDialog::exec);
|
||||
}
|
||||
|
||||
} // namespace mo2::python
|
||||
@@ -0,0 +1,116 @@
|
||||
|
||||
#include "wrappers.h"
|
||||
|
||||
#include "../pybind11_all.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QIcon>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
// IOrganizer must be bring here to properly compile the Python bindings of
|
||||
// plugin requirements
|
||||
#include <imoinfo.h>
|
||||
#include <isavegame.h>
|
||||
#include <isavegameinfowidget.h>
|
||||
#include <pluginrequirements.h>
|
||||
|
||||
namespace py = pybind11;
|
||||
using namespace MOBase;
|
||||
|
||||
namespace mo2::python {
|
||||
|
||||
class PyPluginRequirement : public IPluginRequirement {
|
||||
public:
|
||||
std::optional<Problem> check(IOrganizer* organizer) const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(std::optional<Problem>, IPluginRequirement, check,
|
||||
organizer);
|
||||
};
|
||||
};
|
||||
|
||||
class PySaveGame : public ISaveGame {
|
||||
public:
|
||||
QString getFilepath() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QString, ISaveGame, getFilepath, );
|
||||
}
|
||||
|
||||
QDateTime getCreationTime() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QDateTime, ISaveGame, getCreationTime, );
|
||||
}
|
||||
|
||||
QString getName() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QString, ISaveGame, getName, );
|
||||
}
|
||||
|
||||
QString getSaveGroupIdentifier() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QString, ISaveGame, getSaveGroupIdentifier, );
|
||||
}
|
||||
|
||||
QStringList allFiles() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QStringList, ISaveGame, allFiles, );
|
||||
}
|
||||
|
||||
~PySaveGame() { std::cout << "~PySaveGame()" << std::endl; }
|
||||
};
|
||||
|
||||
class PySaveGameInfoWidget : public ISaveGameInfoWidget {
|
||||
public:
|
||||
// Bring the constructor:
|
||||
using ISaveGameInfoWidget::ISaveGameInfoWidget;
|
||||
|
||||
void setSave(ISaveGame const& save) override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(void, ISaveGameInfoWidget, setSave, &save);
|
||||
}
|
||||
|
||||
~PySaveGameInfoWidget() { std::cout << "~PySaveGameInfoWidget()" << std::endl; }
|
||||
};
|
||||
|
||||
void add_wrapper_bindings(pybind11::module_ m)
|
||||
{
|
||||
// ISaveGame - custom type_caster<> for shared_ptr<> to keep the Python object
|
||||
// alive when returned from Python (see shared_cpp_owner.h)
|
||||
|
||||
py::class_<ISaveGame, PySaveGame, std::shared_ptr<ISaveGame>>(m, "ISaveGame")
|
||||
.def(py::init<>())
|
||||
.def("getFilepath", &ISaveGame::getFilepath)
|
||||
.def("getCreationTime", &ISaveGame::getCreationTime)
|
||||
.def("getName", &ISaveGame::getName)
|
||||
.def("getSaveGroupIdentifier", &ISaveGame::getSaveGroupIdentifier)
|
||||
.def("allFiles", &ISaveGame::allFiles);
|
||||
|
||||
// ISaveGameInfoWidget - custom holder to keep the Python object alive alongside
|
||||
// the widget
|
||||
|
||||
py::class_<ISaveGameInfoWidget, PySaveGameInfoWidget,
|
||||
py::qt::qholder<ISaveGameInfoWidget>>
|
||||
iSaveGameInfoWidget(m, "ISaveGameInfoWidget");
|
||||
iSaveGameInfoWidget.def(py::init<>())
|
||||
.def(py::init<QWidget*>(), py::arg("parent"))
|
||||
.def("setSave", &ISaveGameInfoWidget::setSave, py::arg("save"));
|
||||
py::qt::add_qt_delegate<QWidget>(iSaveGameInfoWidget, "_widget");
|
||||
|
||||
// IPluginRequirement - custom type_caster<> for shared_ptr<> to keep the Python
|
||||
// object alive when returned from Python (see shared_cpp_owner.h)
|
||||
|
||||
py::class_<IPluginRequirement, std::shared_ptr<IPluginRequirement>,
|
||||
PyPluginRequirement>
|
||||
iPluginRequirementClass(m, "IPluginRequirement");
|
||||
|
||||
py::class_<IPluginRequirement::Problem>(iPluginRequirementClass, "Problem")
|
||||
.def(py::init<QString, QString>(), py::arg("short_description"),
|
||||
py::arg("long_description") = "")
|
||||
.def("shortDescription", &IPluginRequirement::Problem::shortDescription)
|
||||
.def("longDescription", &IPluginRequirement::Problem::longDescription);
|
||||
|
||||
iPluginRequirementClass.def("check", &IPluginRequirement::check,
|
||||
py::arg("organizer"));
|
||||
}
|
||||
|
||||
} // namespace mo2::python
|
||||
@@ -0,0 +1,105 @@
|
||||
#ifndef PYTHON_WRAPPERS_WRAPPERS_H
|
||||
#define PYTHON_WRAPPERS_WRAPPERS_H
|
||||
|
||||
#include <any>
|
||||
#include <map>
|
||||
#include <typeindex>
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
|
||||
#include <iplugingame.h>
|
||||
|
||||
namespace mo2::python {
|
||||
|
||||
/**
|
||||
* @brief Add bindings for the various classes in uibase that are not
|
||||
* wrappers (i.e., cannot be extended from Python).
|
||||
*
|
||||
* @param m Python module to add bindings to.
|
||||
*/
|
||||
void add_basic_bindings(pybind11::module_ m);
|
||||
|
||||
/**
|
||||
* @brief Add bindings for the various custom widget classes in uibase that
|
||||
* cannot be extended from Python.
|
||||
*
|
||||
* @param m Python module to add bindings to.
|
||||
*/
|
||||
void add_widget_bindings(pybind11::module_ m);
|
||||
|
||||
/**
|
||||
* @brief Add bindings for the uibase wrappers to the given module. uibase
|
||||
* wrappers include classes from uibase that can be extended from Python but
|
||||
* are neither plugins nor game features (e.g., ISaveGame).
|
||||
*
|
||||
* @param m Python module to add bindings to.
|
||||
*/
|
||||
void add_wrapper_bindings(pybind11::module_ m);
|
||||
|
||||
/**
|
||||
* @brief Add bindings for the various plugin classes in uibase that can be
|
||||
* extended from Python.
|
||||
*
|
||||
* @param m Python module to add bindings to.
|
||||
*/
|
||||
void add_plugins_bindings(pybind11::module_ m);
|
||||
|
||||
/**
|
||||
* @brief Extract plugins from the given object. For each plugin implemented, an
|
||||
* object is returned.
|
||||
*
|
||||
* The returned QObject* are set as owner of the given object so that the Python
|
||||
* object lifetime does not end immediately after returning to C++.
|
||||
*
|
||||
* @param object Python object to extract plugins from.
|
||||
*
|
||||
* @return a QObject* for each plugin implemented by the given object.
|
||||
*/
|
||||
QList<QObject*> extract_plugins(pybind11::object object);
|
||||
|
||||
/**
|
||||
* @brief Add bindings for the various game features classes in uibase that
|
||||
* can be extended from Python.
|
||||
*
|
||||
* @param m Python module to add bindings to.
|
||||
*/
|
||||
void add_game_feature_bindings(pybind11::module_ m);
|
||||
|
||||
/**
|
||||
* @brief Create the game feature corresponding to the given Python type from the
|
||||
* given game.
|
||||
*
|
||||
* @param game Game plugin to extract the feature from.
|
||||
* @param type Type of the feature to extract.
|
||||
*
|
||||
* @return the feature from the game, or None is the game as no such feature.
|
||||
*/
|
||||
pybind11::object extract_feature(MOBase::IPluginGame const& game,
|
||||
pybind11::object type);
|
||||
|
||||
/**
|
||||
* @brief Create Python dictionary mapping game feature classes to the game feature
|
||||
* instances for the given game.
|
||||
*
|
||||
* @param game Game plugin to extract features from.
|
||||
*
|
||||
* @return a python dictionary mapping feature types (in Python) to feature objects.
|
||||
*/
|
||||
pybind11::dict extract_feature_list(MOBase::IPluginGame const& game);
|
||||
|
||||
/**
|
||||
* @brief Convert the given python map of features to a C++ one.
|
||||
*
|
||||
* @param py_features Python features to convert (type to feature).
|
||||
*
|
||||
* @return the map of features.
|
||||
*/
|
||||
std::map<std::type_index, std::any>
|
||||
convert_feature_list(pybind11::dict const& py_features);
|
||||
|
||||
} // namespace mo2::python
|
||||
|
||||
#endif // PYTHON_WRAPPERS_WRAPPERS_H
|
||||
@@ -5,4 +5,4 @@ mo2_configure_plugin(plugin_python
|
||||
WARNINGS OFF
|
||||
EXTRA_TRANSLATIONS ${CMAKE_CURRENT_SOURCE_DIR}/../runner)
|
||||
target_link_libraries(plugin_python PRIVATE pythonrunner)
|
||||
mo2_install_target(plugin_python)
|
||||
mo2_install_target(plugin_python FOLDER)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user