How to make a Krita Python plugin¶
You might have some neat scripts you have written in the Scripter Python runner, but maybe you want to do more with it and run it automatically for instance. Wrapping your script in a plugin can give you much more flexibility and power than running scripts from the Scripter editor.
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 (on Linux ~/.local/share/krita/pykrita). To find your resources folder start Krita and 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.menu item. This will open a dialog box. Click the
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.
- This should always be service.
- This should always be Krita/PythonPlugin for python plugins.
- This should be the name of the plugin folder you just created.
- Whether it is python 2 compatible. If Krita was built with python 2 instead of 3 (
-DENABLE_PYTHON_2=ONin the cmake configuration), then this plugin will not show up in the list.
- 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.
- The name that will show up in the Python Plugin Manager
- 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.
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 the myplugin folder create
- a file called __init__.py
- a file called myplugin.py
- 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.
def createActions(self, window): action = window.createAction("myAction", "My Script", "tools/scripts")
- 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 tools menu.
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() # 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 shortcuts menu when they are present in an .action file. The action file to get our action to be added to 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.
- This should be the unique id you made for your action when creating it in the setup of the extension.
- the name of a possible icon. These will only show up on KDE plasma, because Gnome and Windows users complained they look ugly.
- The text that it will show in the shortcut editor.
- The text it will show when a Qt application specifically calls for ‘what is this’, which is a help action.
- The tool tip, this will show up on hover-over.
- 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.
- This determines when an action is disabled or not.
- This determines activation conditions (e.g. activate only when selection is editable). See the code for examples.
- Default shortcut.
- Whether it is a checkbox or not.
- 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…
- Replace this with an unique ID for your docker that Krita uses to keep track of it.
- The location. These can be DockTornOff, DockTop, DockBottom, DockRight, DockLeft, or DockMinimized
- 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.
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:
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 where 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.
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.