Comment réaliser un module externe Python pour Krita¶
Vous pourriez avoir quelques scripts ingénieux que vous avez écrits avec l'éditeur de scripts Python. Mais, peut être que vous vous faire beaucoup plus et l'exécuter automatiquement par exemple. Intégrer votre script dans un module peut vous offrir bien plus de flexibilité et de puissance que lancer des scripts à partir de l'éditeur de scripts.
Okay, so even if you know python really well, there are some little details to getting Krita to recognize a python plugin. So this page will give an overview how to create the various types of python script unique to Krita.
These mini-tutorials are written for people with a basic understanding of python, and in such a way to encourage experimentation instead of plainly copy and pasting code, so read the text carefully.
Getting Krita to recognize your plugin¶
A script in Krita has two components -- the script directory (holding your script's Python files) and a ".desktop" file that Krita uses to load and register your script. For Krita to load your script both of these must put be in the pykrita
subdirectory of your Krita resources folder (See Gestion des ressources for the paths per operating system). To find your resources folder start Krita and click the menu item. This will open a dialog box. Click the Open Resources Folder button. This should open a file manager on your system at your Krita resources folder. See the API docs under "Auto starting scripts". If there is no pykrita
subfolder in the Krita resources directory use your file manager to create one.
Scripts are identified by a file that ends in a .desktop
extension that contain information about the script itself.
Therefore, for each proper plugin you will need to create a folder, and a desktop file.
The desktop file should look as follows:
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=myplugin
X-Python-2-Compatible=false
X-Krita-Manual=myPluginManual.html
Name=My Own Plugin
Comment=Our very own plugin.
- Type
This should always be service.
- ServiceTypes
This should always be
Krita/PythonPlugin
for python plugins.- X-KDE-Library
This should be the name of the plugin folder you just created.
- X-Python-2-Compatible
Whether it is python 2 compatible. If Krita was built with python 2 instead of 3 (
-DENABLE_PYTHON_2=ON
in the cmake configuration), then this plugin will not show up in the list.- X-Krita-Manual
An Optional Value that will point to the manual item. This is shown in the Python Plugin manager. If it's an HTML file it'll be shown as rich text, if not, it'll be shown as plain text.
- Name
The name that will show up in the Python Plugin Manager.
- Comment
The description that will show up in the Python Plugin Manager.
Krita python plugins need to be python modules, so make sure there's an __init__.py
script, containing something like...
from .myplugin import *
Where .myplugin is the name of the main file of your plugin. If you restart Krita, it now should show this in the Python Plugin Manager in the settings, but it will be grayed out, because there's no myplugin.py. If you hover over disabled plugins, you can see the error with them.
Note
You need to explicitly enable your plugin. Go to the Settings menu, open the Configure Krita dialog and go to the Python Plugin Manager page and enable your plugin.
Résumé¶
In summary, if you want to create a script called myplugin
:
- in your Krita
resources/pykrita
directory create a folder called
myplugin
a file called
myplugin.desktop
- in your Krita
- in the
myplugin
folder create a file called
__init__.py
a file called
myplugin.py
- in the
in the
__init__.py
file put this code:
from .myplugin import *
in the desktop file put this code:
[Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=myplugin X-Python-2-Compatible=false Name=My Own Plugin Comment=Our very own plugin.
write your script in the
myplugin/myplugin.py
file.
Creating an extension¶
Extensions are relatively simple python scripts that run on Krita start. They are made by extending the Extension class, and the most barebones extension looks like this:
from krita import *
class MyExtension(Extension):
def __init__(self, parent):
# This is initialising the parent, always important when subclassing.
super().__init__(parent)
def setup(self):
pass
def createActions(self, window):
pass
# And add the extension to Krita's list of extensions:
Krita.instance().addExtension(MyExtension(Krita.instance()))
This code of course doesn't do anything. Typically, in createActions we add actions to Krita, so we can access our script from the Tools menu.
First, let's create an action. We can do that easily with Window.createAction(). Krita will call createActions for every Window that is created and pass the right window object that we have to use.
So...
def createActions(self, window):
action = window.createAction("myAction", "My Script", "tools/scripts")
- "myAction"
This should be replaced with a unique ID that Krita will use to find the action.
- "My Script"
This is what will be visible in the Menu « Outils ».
If you now restart Krita, you will have an action called "My Script". It still doesn't do anything, because we haven't connected it to a script.
So, let's make a simple export document script. Add the following to the extension class, make sure it is above where you add the extension to Krita:
def exportDocument(self):
# Get the document:
doc = Krita.instance().activeDocument()
# Saving a non-existent document causes crashes, so lets check for that first.
if doc is not None:
# This calls up the save dialog. The save dialog returns a tuple.
fileName = QFileDialog.getSaveFileName()[0]
# And export the document to the fileName location.
# InfoObject is a dictionary with specific export options, but when we make an empty one Krita will use the export defaults.
doc.exportImage(fileName, InfoObject())
And add the import for QFileDialog above with the imports:
from krita import *
from PyQt5.QtWidgets import QFileDialog
Then, to connect the action to the new export document:
def createActions(self, window):
action = window.createAction("myAction", "My Script")
action.triggered.connect(self.exportDocument)
This is an example of a signal/slot connection, which Qt applications like Krita use a lot. We'll go over how to make our own signals and slots a bit later.
Restart Krita and your new action ought to now export the document.
Creating configurable keyboard shortcuts¶
Now, your new action doesn't show up in
.Krita, for various reasons, only adds actions to the Paramètres des raccourcis when they are present in an .action
file. The action file to get our action to be added to the shortcuts should look like this:
<?xml version="1.0" encoding="UTF-8"?>
<ActionCollection version="2" name="Scripts">
<Actions category="Scripts">
<text>My Scripts</text>
<Action name="myAction">
<icon></icon>
<text>My Script</text>
<whatsThis></whatsThis>
<toolTip></toolTip>
<iconText></iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>ctrl+alt+shift+p</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
</ActionCollection>
- <text>My Scripts</text>
This will create a sub-category under scripts called "My Scripts" to add your shortcuts to.
- nom
This should be the unique ID you made for your action when creating it in the setup of the extension.
- icône
The name of a possible icon. These will only show up on KDE plasma, because Gnome and Windows users complained they look ugly.
- texte
The text that it will show in the shortcut editor.
- whatsThis
The text it will show when a Qt application specifically calls for 'what is this', which is a help action.
- toolTip
The tool tip, this will show up on hover-over.
- iconText
The text it will show when displayed in a toolbar. So for example, "Resize Image to New Size" could be shortened to "Resize Image" to save space, so we'd put that in here.
- activationFlags
This determines when an action is disabled or not.
- activationConditions
This determines activation conditions (e.g. activate only when selection is editable). See the code for examples.
- shortcut
Default shortcut.
- isCheckable
Whether it is a checkbox or not.
- statusTip
The status tip that is displayed on a status bar.
Save this file as myplugin.action
where myplugin is the name of your plugin. The action file should be saved, not in the pykrita resources folder, but rather in a resources folder named "actions". (So, share/pykrita
is where the python plugins and desktop files go, and share/actions
is where the action files go) Restart Krita. The shortcut should now show up in the shortcut action list.
Creating a docker¶
Creating a custom docker is much like creating an extension. Dockers are in some ways a little easier, but they also require more use of widgets. This is the barebones docker code:
from PyQt5.QtWidgets import *
from krita import *
class MyDocker(DockWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("My Docker")
def canvasChanged(self, canvas):
pass
Krita.instance().addDockWidgetFactory(DockWidgetFactory("myDocker", DockWidgetFactoryBase.DockRight, MyDocker))
The window title is how it will appear in the docker list in Krita. canvasChanged always needs to be present, but you don't have to do anything with it, so hence just 'pass'.
For the addDockWidgetFactory...
- "myDocker"
Replace this with a unique ID for your docker that Krita uses to keep track of it.
- DockWidgetFactoryBase.DockRight
The location. These can be
DockTornOff
,DockTop
,DockBottom
,DockRight
,DockLeft
, orDockMinimized
- MyDocker
Replace this with the class name of the docker you want to add.
So, if we add our export document function we created in the extension section to this docker code, how do we allow the user to activate it? First, we'll need to do some Qt GUI coding: Let's add a button!
By default, Krita uses PyQt, but its documentation is pretty bad, mostly because the regular Qt documentation is really good, and you'll often find that the PyQt documentation of a class, say, QWidget is like a weird copy of the regular Qt documentation for that class.
Anyway, what we need to do first is that we need to create a QWidget
, it's not very complicated, under setWindowTitle
, add:
mainWidget = QWidget(self)
self.setWidget(mainWidget)
Then, we create a button:
buttonExportDocument = QPushButton("Export Document", mainWidget)
Now, to connect the button to our function, we'll need to look at the signals in the documentation. QPushButton has no unique signals of its own, but it does say it inherits 4 signals from QAbstractButton, which means that we can use those too. In our case, we want clicked.
buttonExportDocument.clicked.connect(self.exportDocument)
If we now restart Krita, we'll have a new docker and in that docker there's a button. Clicking on the button will call up the export function.
However, the button looks aligned a bit oddly. That's because our mainWidget
has no layout. Let's quickly do that:
mainWidget.setLayout(QVBoxLayout())
mainWidget.layout().addWidget(buttonExportDocument)
Qt has several layouts, but the QHBoxLayout and the QVBoxLayout are the easiest to use, they just arrange widgets horizontally or vertically.
Restart Krita and the button should now be laid out nicely.
PyQt Signals and Slots¶
We've already been using PyQt signals and slots already, but there are times when you want to create your own signals and slots. As PyQt's documentation is pretty difficult to understand, and the way how signals and slots are created is very different from C++ Qt, we're explaining it here:
All python functions you make in PyQt can be understood as slots, meaning that they can be connected to signals like Action.triggered
or QPushButton.clicked
. However, QCheckBox
has a signal for toggled, which sends a boolean. How do we get our function to accept that boolean?
First, make sure you have the right import for making custom slots:
from PyQt5.QtCore import pyqtSlot
(If there's from PyQt5.QtCore import *
already in the list of imports, then you won't have to do this, of course.)
Then, you need to add a PyQt slot definition before your function:
@pyqtSlot(bool)
def myFunction(self, enabled):
enabledString = "disabled"
if (enabled == True):
enabledString = "enabled"
print("The checkbox is"+enabledString)
Then, when you have created your checkbox, you can do something like myCheckbox.toggled.connect(self.myFunction).
Similarly, to make your own PyQt signals, you do the following:
# signal name is added to the member variables of the class
signal_name = pyqtSignal(bool, name='signalName')
def emitMySignal(self):
# And this is how you trigger the signal to be emitted.
self.signal_name.emit(True)
And use the right import:
from PyQt5.QtCore import pyqtSignal
To emit or create slots for objects that aren't standard python objects, you only have to put their names between quotation marks.
A note on unit tests¶
If you want to write unit tests for your plugin, have a look at the mock krita module.
Conclusion¶
Okay, so that covers all the Krita specific details for creating python plugins. It doesn't handle how to parse the pixel data, or best practices with documents, but if you have a little bit of experience with python you should be able to start creating your own plugins.
As always, read the code carefully and read the API docs for python, Krita and Qt carefully to see what is possible, and you'll get pretty far.