Commit 2a1bf73c authored by Boudewijn Rempt's avatar Boudewijn Rempt

Add Python bindings to libkis

This consists of three parts:

* the sip wrapper of libkis
* the plugin that loads plugins created in python
* and some sample python plugins, like the scripter ad-hoc scripting
ide-let created by Eliakin Costa
parent 636305c5
......@@ -24,3 +24,5 @@ if (CMAKE_COMPILER_IS_GNUCC)
add_subdirectory( gmic )
endif()
endif()
add_subdirectory( pykrita )
find_package(PythonLibrary)
set_package_properties(PythonLibrary PROPERTIES
DESCRIPTION "Python Library"
URL "http://www.python.org"
TYPE OPTIONAL
PURPOSE "Required by the Krita PyQt plugin")
macro_bool_to_01(PYTHONLIBS_FOUND HAVE_PYTHONLIBS)
find_package(SIP "4.18.0")
set_package_properties(SIP PROPERTIES
DESCRIPTION "Support for generating SIP Python bindings"
URL "https://www.riverbankcomputing.com/software/sip/download"
TYPE OPTIONAL
PURPOSE "Required by the Krita PyQt plugin")
macro_bool_to_01(SIP_FOUND HAVE_SIP)
find_package(PyQt5 "5.6.0")
set_package_properties(PyQt5 PROPERTIES
DESCRIPTION "Python bindings for Qt5."
URL "https://www.riverbankcomputing.com/software/pyqt/download5"
TYPE OPTIONAL
PURPOSE "Required by the Krita PyQt plugin")
macro_bool_to_01(PYQT5_FOUND HAVE_PYQT5)
if (HAVE_PYQT5 AND HAVE_SIP AND HAVE_PYTHONLIBS)
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${SIP_INCLUDE_DIR} ${PYTHON_INCLUDE_PATH})
add_subdirectory(sip)
add_subdirectory(plugin)
add_subdirectory(kritarunner)
endif ()
class Krita :
Properties:
Actions : QList<Action*>
ActiveDocument : Document*
Batchmode : bool
Documents : QList<Document*>
Filters : QList<Filter*>
Generators : QList<Generator*>
Notifier : Notifier*
Preferences : InfoObject*
Version : QString
Views : QList<View*>
Windows : QList<Window*>
Resources : QList<Resource*>
Slots:
addDockWidget(DockWidget *dockWidget) : void
addAction(Action *action) : void
closeApplication() : bool
createDocument() : Document*
openDocument() : Document*
openWindow() : Window*
class Notifier :
Properties:
Active : bool
Signals:
applicationStarted()
applicationClosed()
imageCreated(Document *image)
imageLoaded(Document *image)
imageSaved(Document *image)
imageClosed(Document *image)
nodeCreated(Document *node)
class Document :
Properties:
ActiveNode : Node*
ColorDepth : ColorDepth*
ColorManager : ColorManager*
ColorModel : ColorModel*
ColorProfile : ColorProfile*
DocumentInfo : InfoObject*
FileName : QString
Height : int
MetaData : InfoObject*
Name : QString
Resolution : int
RootNode : Node*
Selection : Selection*
Width : int
PixelData : QByteArray
Slots:
clone() : Document *
close() : bool
convert(const QString &colorModel, const ColorProfile *profile) : bool
crop(int x, int y, int w, int h) : void
Export(const InfoObject &exportConfiguration) : bool
Flatten() : void
ResizeImage(int w, int h) : void
Save(const QString &url) : bool
SaveAs(const QString &url) : bool
OpenView() : void
CreateNode(const QString &name, const QString &nodeType) : Node*
class Node :
Properties:
AlphaLocked : bool
BlendingMode : QString
Channels : QList<Channel*>
ChildNodes : QList<Node*>
ColorDepth : ColorDepth*
ColorLabel : QString
ColorModel : ColorModel*
ColorProfile : ColorProfile*
InheritAlpha : bool
Locked : bool
Name : QString
Opacity : int
ParentNode : Node*
Type : QString
Visible : bool
MetaDataInfo : InfoObject*
Generator : Generator*
Filter : Filter*
Transformation : Transformation*
Selection : Selection*
FileName : QString
PixelData : QByteArray
Slots:
move(int x, int y) : void
moveToParent(Node *parent) : void
remove() : void
duplicate() : Node*
class Channel :
Properties:
visible : bool
class Filter :
Properties:
Configuration : InfoObject*
Slots:
Apply(int x, int y, int w, int h) : void
class Generator :
Properties:
Configuration : InfoObject*
Slots:
CreateNode() : Node*
class Action :
Properties:
Name : QString
Menu : QString
Checkable : bool
Checked : bool
Shortcut : QString
Visible : bool
Enabled : bool
Slots:
Trigger() : void
Toggle(bool toggle) : void
Signals:
Toggled(bool toggle)
Triggered()
class Canvas :
Properties:
Document : Document*
ZoomLevel : int
Rotation : int
Mirror : bool
ColorManager : ColorManager*
class ColorManager :
Properties:
Type : QString
OcioSettings : InfoObject*
class View :
Properties:
Window : Window*
Document : Document*
Visible : bool
Canvas : Canvas*
Slots:
close(bool confirm) : void
#class Window :
# Properties:
# Views : QList<View*>
# ViewMode : QString
# Slots:
# close(bool confirm) : void
class DockWidget :
Properties:
Canvas : Canvas*
Slots:
canvasChanged(Canvas *canvas) : void
class ColorDepth :
Properties:
depth : int
class ColorModel :
Properties:
colorModelID : QString
class ColorProfile :
Properties:
name : QString
class InfoObject :
Properties:
properties : QMap<QString, QVariant>
Slots:
setProperty(const QString &key, QVariant value) : void
property(const QString &key) : QVariant
class Selection :
Properties:
Width : int
Height : int
X : int
Y : int
Type : QString
Slots:
clear() : void
contract(int value) : void
copy(int x, int y, int w, int h) : Selection*
cut(Node* node) : void
deselect() : void
expand(int value) : void
feather(int value) : void
fill(Node* node) : void
grow(int value) : void
invert() : void
resize(int w, int h) : void
rotate(int degrees) : void
select(int x, int y, int w, int h, int value) : void
selectAll(Node *node) : void
class Resource :
Properties:
Type : QString
Name : QString
Filename : QString
class Transformation :
Properties:
Definition : QString
class Dummy :
$$$$$ END $$$$$
This diff is collapsed.
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../plugin
${CMAKE_CURRENT_BINARY_DIR}/../plugin
${CMAKE_CURRENT_SOURCE_DIR}/../libkis
${CMAKE_CURRENT_BINARY_DIR}/../libkis
)
set(kritarunner_SRCS main.cpp
../plugin/engine.cpp
../plugin/plugin.cpp
../plugin/pyqtpluginsettings.cpp
../plugin/utilities.cpp
)
add_executable(kritarunner ${kritarunner_SRCS})
target_link_libraries(kritarunner
PRIVATE
${PYTHON_LIBRARY}
kritaui
kritalibkis
Qt5::Core
Qt5::Gui
Qt5::Widgets
Qt5::Xml
Qt5::Network
Qt5::PrintSupport
Qt5::Svg
Qt5::Concurrent)
install(TARGETS kritarunner ${INSTALL_TARGETS_DEFAULT_ARGS})
/*
* Copyright (c) 2016 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <stdlib.h>
#include <QString>
#include <QCommandLineParser>
#include <QCommandLineOption>
#include <KisApplication.h>
#include <KoGlobal.h>
#include <resources/KoHashGeneratorProvider.h>
#include "kis_md5_generator.h"
#include <opengl/kis_opengl.h>
#include <engine.h>
#include <utilities.h>
extern "C" int main(int argc, char **argv)
{
// The global initialization of the random generator
qsrand(time(0));
KLocalizedString::setApplicationDomain("kritarunner");
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true);
KisOpenGL::setDefaultFormat();
QLoggingCategory::setFilterRules("krita*.debug=false\n"
"krita*.warning=false\n"
"krita.tabletlog=false");
// first create the application so we can create a pixmap
KisApplication app("kritarunner", argc, argv);
app.setApplicationDisplayName("Krita Script Runner");
app.setApplicationName("kritarunner");
app.setOrganizationDomain("krita.org");
QCommandLineParser parser;
parser.setApplicationDescription("kritarunner executes one python script and then returns.");
parser.addVersionOption();
parser.addHelpOption();
QCommandLineOption scriptOption(QStringList() << "s" << "script", "The script to run. Do not append the .py extension.", "script");
parser.addOption(scriptOption);
QCommandLineOption functionOption(QStringList() << "f" << "function",
"The function to call (by default __main__ is called).", "function", "__main__");
parser.addOption(functionOption);
parser.addPositionalArgument("[argument(s)]", "The argumetns for the script");
parser.process(app);
if (!parser.isSet(scriptOption)) {
qDebug("No script given, aborting.");
return 1;
}
qDebug() << "running:" << parser.value(scriptOption) << parser.value(functionOption);
qDebug() << parser.positionalArguments();
KoHashGeneratorProvider::instance()->setGenerator("MD5", new KisMD5Generator());
KoGlobal::initialize();
app.addResourceTypes();
app.loadResources();
app.loadPlugins();
QByteArray pythonPath = qgetenv("PYTHONPATH");
qDebug() << "\tPython path:" << pythonPath;
qDebug() << "Creating engine";
PyKrita::Engine engine;
QString r = engine.tryInitializeGetFailureReason();
if (!r.isEmpty()) {
qDebug("Could not initialize the Python engine");
return 1;
}
qDebug() << "Try to import the pykrita module";
PyKrita::Python py = PyKrita::Python();
PyObject* pykritaPackage = py.moduleImport("pykrita");
pykritaPackage = py.moduleImport("krita");
if (!pykritaPackage) {
qDebug("Cannot load the PyKrita module, aborting");
return 1;
}
PyObject *argsList = PyList_New(0);
Q_FOREACH(const QString arg, parser.positionalArguments()) {
PyObject* const u = py.unicode(arg);
PyList_Append(argsList, u);
Py_DECREF(u);
}
PyObject *args = PyTuple_New(1);
PyTuple_SetItem(args, 0, argsList);
py.functionCall(parser.value(functionOption).toUtf8().constData(), parser.value(scriptOption).toUtf8().constData(), args);
Py_DECREF(argsList);
Py_DECREF(args);
app.quit();
return 0;
}
* Action: a QAction that can trigger something, be embedded in the menu or a toolbar
* Application: the central application object, the root of the whole tree
* Canvas: displays a given image at a certain zoom level, rotation and mirroring
* Channel:
* ColorDepth
* ColorManager: icc or ocio
* ColorModel
* ColorProfile
* DockWidget: can be created by a factory on startup and can handle Canvas objects
* Document: document/image
* Exporter: can export a given document
* Filter: can apply itself to a node
* Generator: can generate a new node with the given generator and configuration
* Importer: can open a given document
* InfoObject
* Node
* Notifier: sends on out signals when documents are opened or closed etc.
* PreferenceManager: allows reading and changing the settings
* Resource
* Selection
* Transformation
* View: contains an image
* Window: contains one or more views
# NOTE Disable trivial Qt keywords due conflicts w/ some Python.h header
# (at least version 3.3 of it has a member PyType_Spec::slots)
add_definitions(-DQT_NO_KEYWORDS)
configure_file(config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h)
set(SOURCES
plugin.cpp
pyqtpluginsettings.cpp
utilities.cpp
engine.cpp
)
ki18n_wrap_ui(SOURCES
info.ui
manager.ui
)
add_library(kritapykrita MODULE ${SOURCES})
target_link_libraries(
kritapykrita
${PYTHON_LIBRARY}
kritaui
kritalibkis
)
install(TARGETS kritapykrita DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
# Install "built-in" api
install(
DIRECTORY krita
DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita
FILES_MATCHING PATTERN "*.py"
)
add_subdirectory(plugins)
#add_subdirectory(test)
// This file is part of PyKrita, Krita' Python scripting plugin.
//
// Copyright (C) 2006 Paul Giannaros <paul@giannaros.org>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) version 3.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public License
// along with this library; see the file COPYING.LIB. If not, write to
// the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
// Boston, MA 02110-1301, USA.
#define PYKRITA_PYTHON_LIBRARY "${PYTHON_LIBRARY}"
#define PYKRITA_PYTHON_SITE_PACKAGES_INSTALL_DIR "${PYTHON_SITE_PACKAGES_INSTALL_DIR}"
This diff is collapsed.
/*
* This file is part of PyKrita, Krita' Python scripting plugin.
*
* Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com>
* Copyright (C) 2014-2016 Boudewijn Rempt <boud@valdyas.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) version 3.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef __PYKRITA_ENGINE_H__
# define __PYKRITA_ENGINE_H__
# include <Python.h>
# include "version_checker.h"
# include <QAbstractItemModel>
# include <QList>
# include <QStringList>
namespace PyKrita
{
/**
* @brief The PyPlugin class describes a plugin written in Python and loaded into the system
*/
class PyPlugin {
public:
PyPlugin()
{
m_properties["X-Python-Dependencies"] = QStringList();
m_properties["X-Python-2-Dependencies"] = QStringList();
}
QString name() const
{
return m_name;
}
QString library() const
{
return m_libraryPath;
}
QVariant property(const QString &name) const
{
return m_properties.value(name, "");
}
QString comment() const
{
return m_comment;
}
QString m_name;
QString m_libraryPath;
QMap<QString, QVariant> m_properties;
QString m_comment;
};
class Python; // fwd decl
/**
* The Engine class hosts the Python interpreter, loading
* it into memory within Krita, and then with finding and
* loading all of the PyKrita plugins.
*
* \attention Qt/KDE does not use exceptions (unfortunately),
* so this class must be initialized in two steps:
* - create an instance (via constructor)
* - try to initialize the rest (via \c Engine::tryInitializeGetFailureReason())
* If latter returns a non empty (failure reason) string, the only member
* can be called is conversion to boolean! (which is implemented as safe-bool idiom [1])
* Calling others leads to UB!
*
* \sa [1] http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Safe_bool
*/
class Engine : public QAbstractItemModel
{
Q_OBJECT
typedef void (Engine::*bool_type)() const;
void unspecified_true_bool_type() const {}
public:
/// \todo Turn into a class w/ accessors
class PluginState
{
public:
/// \name Immutable accessors
//@{
QString pythonModuleName() const;
const QString& errorReason() const;
bool isEnabled() const;
bool isBroken() const;
bool isUnstable() const;
//@}
private:
friend class Engine;
PluginState();
/// Transfort Python module name into a file path part
QString moduleFilePathPart() const;
PyPlugin m_pythonPlugin;
QString m_pythonModule;