mobase
is a ModOrganizer2 Python API.
It provides access to the part of
uibase
C++ API from
Python through pybind11
.
Important: All (most) files should include pybind11_all.h
(either directly
or through another header) to get proper type_caster
available.
mobase.cpp
contains thePYBIND11_MODULE
definition ofmobase
but is otherwise the entrypoint for other functions.wrappers.h
contains the declaration of most functions implemented underwrappers/
.- The other files under
wrappers/
contains bindings and trampoline classes (see below) foruibase
classes.basic_classes.cpp
contains the bindings for most classes that cannot be extended in Python (IOrganizer
,IModInterface
, etc.)game_features.cpp
contains the bindings and trampoline classes for game features.pyfiletree.h
andpyfiletree.cpp
contains bindings for theIFileTree
-related classes.pyplugins.h
contains the trampoline classes for theIPluginXXX
classes andpyplugins.cpp
the bindings.pyplugins.h
is required since the trampoline classes are tagged withQ_OBJECT
, and MOC does not work if the classes are declared in a C++ file.pyplugins.cpp
also contains theextract_plugins
function inmobase.private
that is used to extract plugins from Python object in the runner.
widgets.cpp
contains the bindings for the widget classes.wrappers.cpp
contains the trampoline and bindings for non-plugin classes that can be extended through Python.
Updating or adding classes that cannot be extended through Python is quite easily.
One simply needs to declare the appropriate py::class_
or add new .def()
.
See below for things to remember when creating pybind11 bindings.
Similar to classes that cannot be extended through Python, see above.
To extend plugins, simply update the trampoline classes in pyplugins.h
and the
bindings in pyplugins.cpp
.
Note: For new plugins, simply look at the existing one.
To extend or expose game features:
- Create (if there is not already one) a trampoline class for the feature in
game_features.cpp
.- Add implementation of missing functions if required.
- Add the bindings in
add_game_feature_bindings
ingame_features.cpp
. - For new feature, add the feature type to
GameFeaturesHelper::GameFeatures
ingame_features.cpp
.
Non-plugin classes should be added to the wrappers.cpp
file and should be exposed
with std::shared_ptr<>
or qobject_holder<>
holders.
- If the classes extends
QObject
, use aqobject_holder
. - Otherwise use a
std::shared_ptr<>
holder and add aMO2_PYBIND11_SHARED_CPP_HOLDER
declaration inpybind11_all.h
.
Trampoline can be defined directly in wrappers.cpp
, and bindings in the appropriate
function.
See the existing classes for example.
Important:
You need to make sure that uibase
manipulates such classes through
std::shared_ptr<>
(unless those inherit QObject
).
Using std::unique_ptr<>
is not possible since std::unique_ptr<>
cannot have custom
runtime-specified deleters.
Here are a few things to remember when creating bindings:
- If a function has multiple overloads that can conflict in Python, the more complex one must be defined first as pybind11 will try calling them in order.
- If a C++ function expect a
QString
,QFileInfo
orQDir
that represents a file or a directory, wrapping the function withwrap_for_filepath
orwrap_for_directory
is a good idea. This allows Python to call the function withpathlib.Path
. - Most of the C++ function taking a reference to modify in C++ cannot be directly
exposed in Python since Python cannot modify reference to simple type (e.g.
QString&
orint&
). The best way to expose such function is to bind a lambda that returns a variant from Python, e.g.
// assume the C++ function is QString fn(QString& foo, QString const& bar, int& maz);
m.def("function", [](QString& foo, QString const& bar, int& maz) {
// call the function
auto ret = function(foo, bar, maz);
// make a tuple containing the return value (if there is one), and the modified
// values passed by reference
return std::make_tuple(ret, foo, maz);
});