About Python, Krita, Qt5 and PyQt5

In this post you learn a little about the underlying framework that Krita uses to interface via Python. The Krita application is written in c++ using an application framework called Qt5. Krita provides access to its functionality for scripting by wrapping the c++ code and presenting it to Python. Krita is created on top of Qt5 objects, so most of the objects you encounter while scripting will be wrapped Qt5 objects. There is an existing library for Python wrapping Qt5 objects called PyQt5. So, when you are accessing objects from your Python scripts, you need to import the equivalent PyQt5 objects. For example, if you want to use a Qt5 QSettings object you will need to import the equivalent wrapped object from PyQt5.Core:

from PyQt5.QtCore import QSettings

Warning: Whether or not this will work depends on how you have obtained your version of Krita. If you are running from a packaged version – such as the Windows version or an appimage for Linux, then PyQt5 will be present in your environment. When I work with Krita that I have compiled from source, PyQt5 is not present and I need to manually install the relevant package (in my case python3-qt5) to my system with my package manner.

Because these objects are wrapped, you need to make sure that if you overwrite the object’s constructor (__init__) then you must make sure that you call the superclass’s constructor in your overwritten method. If you don’t then the c++ initialisation code will not be executed and your object will break (as none of the underlying c++ parts of the object are present). You did this in the extension skeleton with this code:

class MyExtension(Extension):

    def __init__(self, parent):
        super().__init__(parent)

In this code, the c++ constructor code for the Extension object is executed, with parent (ultimately, a reference to the running instance of Krita) passed as a parameter.

Keep References to Avoid Taking Out the Trash

Both Python and Qt5 are garbage collected languages. For this reason you need to take care to keep references to the objects you create. So, for example don’t simply create a widget, keep a reference to the created widget. If you have worked with Tkinter, you should be familiar with this.

Example:

label = QLabel("Some text")

not:

QLabel("some text").show()

Qt5’s “Event” Framework – Signals and Slots

Qt5 uses a system of signals and slots to connect its widgets with user actions. If you have used events before (eg with Tkinter programs) you can think of signals as being events and slots as callbacks. The big exception with this is that you don’t need to percolate or consume a signal – any number of slots can be connected in parallel with a single signal and they don’t need to manage each other’s access to the signal.

In your code:

    def setup(self):
        app = Krita.instance()
        action = app.createAction("name_of_action", "Menu Entry for Script")
        # parameter 1 =  the name that Krita uses to identify the action
        # parameter 2 = the text to be added to the menu entry for this script
        action.triggered.connect(self.action_triggered)
        
    def action_triggered(self):
        pass # your active code goes here. 

You create an action (a kind of object):

action = app.createAction("name_of_action", "Menu Entry for Script")

This action is displayed in the Krita UI by a menu entry in the Tools->Scripts menu. When a user clicks on this entry, it generates a triggered signal. The code:

action.triggered.connect(self.action_triggered)

connects that signal to the method self.action_triggered. A function that you connect a signal to is called a slot. Some signals pass additional information through parameters.

If you change the code in your action_triggered method, you can test that the code is run when you click the menu entry. For now, keep it simple and just add:

        print("In action_triggered")

You will need to run Krita from the console to see this. Now, each time you click your extension’s menu entry In action_triggered will be output to the console. In your own extensions you are going to want to use your slot to start running your code, either to process some information or present a user interface to get user data.

Leave a comment