Як створити додаток до Krita мовою Python

Можливо, ви вже спробувати написати якісь скрипти за допомогою додатка Скриптер і Python, але, ймовірно, хочете чогось більшого, наприклад автоматичного запуску скриптів. Створення вашого скрипту у форматі додатка надасть вашим скриптам гнучкості та ширших можливостей, якщо порівнювати зі скриптами, які запускаються з редактора Скриптера.

Гаразд, навіть якщо ви знаєтеся на Python, є дещо, що потрібно знати, щоб Krita змогла розпізнати додаток Python. У цьому розділі наведено огляд питань, які пов’язано із особливостями створення різних типів сценаріїв Python, які є унікальними Krita.

Ці мініпідручники написано для тих, хто має базові знання з Python, у такий спосіб, щоб заохотити читачів до експериментування, а не до простого копіювання та вставлення коду. Через це, закликаємо вас до уважного читання тексту цього розділу.

Розпізнавання вашого додатка у Krita

Сценарій у Krita складається з двох компонентів — каталогу сценарію (де містяться файли вашого сценарію Python) та файла «.desktop», який Krita використовує для завантаження та реєстрації вашого сценарію. Щоб Krita могла завантажити ваш сценарій, обидва ці компоненти мають зберігатися у підкаталозі pykrita вашої теки ресурсів Krita (див. Керування ресурсами, щоб дізнатися більше про шляхи у різних операційних системах). Щоб визначити, яку адресу має тека ресурсів у вашій системі, запустіть Krita і скористайтеся пунктом меню Параметри ‣ Керування ресурсами…. У відповідь програма відкриє діалогове вікно. Натисніть у ньому кнопку Відкрити теку ресурсів. У відповідь має бути відкрито програму для керування файлами вашої системи із текою ресурсів Krita. Див. документацію з програмного інтерфейсу, розділ «Auto starting scripts». Якщо у теці ресурсів Krita немає підтеки pykrita, скористайтеся можливостями програми для керування файлами, щоб її створити.

Сценарії розпізнаються за файлом, назва якого завершується суфіксом .desktop і який містить відомості щодо самого сценарію.

Отже, для кожного належно форматованого додатка вам слід створити теку та файл desktop.

Вміст файл desktop має бути таким:

[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

Тут завжди має бути вказано «service».

ServiceTypes

Значенням цього параметра для додатків мовою Python завжди має бути Krita/PythonPlugin.

X-KDE-Library

Для цього параметра слід вказати значення назви теки додатка, яку ви щойно створили.

X-Python-2-Compatible

Визначає сумісність із Python 2. Якщо Krita було зібрано з використанням Python 2, а не 3 (-DENABLE_PYTHON_2=ON у налаштуваннях cmake), пункт цього додатка у списку показано не буде.

X-Krita-Manual

Необов’язкове значення, яке містить посилання на розділ підручника. Цей розділ буде показано у засобі керування додатками Python. Якщо це файл HTML, його має бути показано як форматований текст, якщо ні, його буде показано як звичайний текст.

Name

Назва, яку буде показано у засобі керування додатками Python.

Comment

Опис, який буде показано у засобі керування додатками Python.

Додатки мовою Python до Krita мають бути модулями Python, тому має бути скрипт __init__.py, який міститиме код, подібний до такого:

from .myplugin import *

Де .myplugin — назва головного файла вашого додатка. Якщо ви перезапустите Krita, у засобі керування додатками Python буде показано відповідний пункт у параметрах, але пункт буде неактивним, оскільки ще немає myplugin.py. Якщо ви наведете вказівник миші на пункт вимкненого додатка, програма покаже контекстну панель із підказкою щодо того, яка помилка сталася під час спроби завантажити додаток.

Примітка

Вам слід явним чином увімкнути цей додаток. Розгорніть меню «Параметри», відкрийте діалогове вікно налаштовування Krita і перейдіть на сторінку керування додатками Python. На цій сторінці позначте пункт вашого додатка.

Summary

Загалом, якщо ви хочете створити сценарій із назвою myplugin:

  • у вашому каталозі resources/pykrita Krita створіть
    • теку із назвою myplugin

    • файл із назвою myplugin.desktop

  • у теці myplugin створіть
    • файл із назвою __init__.py

    • файл із назвою myplugin.py

  • у файлі __init__.py впишіть такий код:

from .myplugin import *
  • до файла desktop впишіть такий код:

    [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.
    
  • запишіть ваш сценарій до файла :file`myplugin/myplugin.py`.

Створення розширення

Розширення є відносно простими сценаріями python, які запускаються під час завантаження Krita. Розширення створюються розширенням класу Extension. Найпростіше розширення виглядає так:

from krita import *

class MyExtension(Extension):

    def __init__(self, parent):
        # Ініціалізація parent, завжди важливо, якщо створюємо підклас.
        super().__init__(parent)

    def setup(self):
        pass

    def createActions(self, window):
        pass

# І додаємо розширення до списку розширень Krita:
Krita.instance().addExtension(MyExtension(Krita.instance()))

Цей код, звичайно ж, не виконує ніяких дій. Типово, у createActions додають дії для Krita, отже, доступ до нашого сценарію можна отримати за допомогою меню Інструменти.

Спочатку, створімо дію. Зробити це дуже просто за допомогою Window.createAction(). Krita викликатиме createActions для кожного створеного вікна Window і передаватиме відповідний об’єкт вікна, яким нам слід користуватися.

Отже…

def createActions(self, window):
    action = window.createAction("myAction", "My Script", "tools/scripts")
«myAction»

Це слід замінити унікальним ідентифікатором, який Krita використовуватиме для пошуку дії.

«My Script»

Це пункт, який буде показано у меню «Інструменти».

Якщо ви перезапустите Krita, ви побачите дію із назвою «My Script». Її вибір все ще не призводитиме до якихось наслідків, оскільки ми ще не пов’язали її зі сценарієм.

Отже, створімо простий сценарій експортування документа. Додайте вказаний нижче код до класу extension. Переконайтеся, що код розташовано вище місця додавання розширення до Krita:

def exportDocument(self):
    # Отримуємо документ:
    doc =  Krita.instance().activeDocument()
    # Збереження документа, якого не існує, призведе до аварійного завершення роботи, отже, спочатку перевіримо.
    if doc is not None:
        # Викликаємо діалогове вікно зберігання. Об'єкт повертає кортеж значень.
        fileName = QFileDialog.getSaveFileName()[0]
        # І експортуємо документ до розташування fileName.
        # InfoObject є словником зі специфічними параметрами експортування, але коли ми створюємо порожній, Krita використовуватиме типові параметри експортування.
        doc.exportImage(fileName, InfoObject())

І додайте імпортування QFileDialog згори:

from krita import *
from PyQt5.QtWidgets import QFileDialog

Далі, для встановлення з’єднання дій із новим експортованим документом:

def createActions(self, window):
    action = window.createAction("myAction", "My Script")
    action.triggered.connect(self.exportDocument)

Це приклад з’єднання сигнал-слот, який широко використовується у програмах Qt, зокрема Krita. Питання створення наших власних сигналів і слотів ми обговоримо нижче.

Перезапустіть Krita. Ваша нова дія тепер може експортувати документ.

Створення придатних до налаштовування клавіатурних скорочень

Вашої нової дії немає у списку Параметри ‣ Налаштувати Krita ‣ Клавіатурні скорочення.

Krita, з різних причин, додає пункти дій до клавіатурних скорочень, лише якщо їх записано до файла .action. Файл action, який слід додати, щоб нашу дію було додано до списку дій для скорочень, має виглядати десь так:

<?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>

У результаті буде створено підкатегорію у сценаріях із назвою «My Scripts» для додавання ваших клавіатурних скорочень.

name

Це має бути унікальний ідентифікатор, який ви створили для вашої дії під час створення її у налаштування у розширенні.

icon

Назва можливої піктограми. Піктограми буде показано лише у Плазмі KDE, оскільки користувачі GNOME і Windows скаржилися на те, що ці піктограми виглядають потворно.

text

Текст, який буде показано у редакторі клавіатурних скорочень.

whatsThis

Текст, який буде показано, коли програма на основі Qt викличе дію «Що це?», яка є одним із різновидів контекстної довідки і викликати яку можна за допомогою відповідного пункту у меню «Довідка».

toolTip

Підказка, яку буде показано на контекстній панелі у відповідь на наведення вказівника миші.

iconText

Текст, який буде показано для пункту на панелі інструментів. Отже, наприклад, для дії «Змінити розмір зображення до нового» запис можна скоротити до «Змінити розмір» так, щоб пункт вмістився на панелі інструментів.

activationFlags

Це визначає, буде дію вимкнено чи ні.

activationConditions

Визначає умови активації (наприклад «активувати, лише якщо позначена ділянка придатна до редагування»). Див. приклади у коді.

shortcut

Типове клавіатурне скорочення.

isCheckable

Визначає, чи буде поле для позначки.

statusTip

Підказка щодо стану, яку буде показано на смужці стану.

Збережіть цей файл із назвою myplugin.action, де myplugin — назва вашого додатка. Файл дій має бути збережено не у теці ресурсів pykrita, а у теці ресурсів із назвою «actions». (Отже, share/pykrita — місце для додатків Python і файлів desktop, а share/actions — місце для файлів дій (action).) Перезапустіть Krita. Пункт дії має з’явитися у списку клавіатурних скорочень.

Створення бічної панелі

Створення нетипової бічної панелі дуже схоже на створення розширення. Певним чином, створення бічних панелей є простішим, але для них треба ширше використовувати віджети. Ось базовий код бічної панелі:

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))

Заголовок вікна, який буде показано у списку бічних панелей у Krita. canvasChanged має бути завжди, але робити з ним щось не обов’язково, отже, просто «пропускаємо» його.

Для addDockWidgetFactory…

«myDocker»

Замініть цей рядок на унікальний ідентифікатор вашої бічної панелі, яким Krita користуватиметься для стеження за нею.

DockWidgetFactoryBase.DockRight

Розташування. Можливі варіанти: DockTornOff, DockTop, DockBottom, DockRight, DockLeft та DockMinimized

MyDocker

Замініть цей рядок назвою класу бічної панелі, яку ви хочете додати.

Отже, нехай ми додали нашу функцію експортування документа, яку ми створили у розділі, який присвячено розширенням, до коду цієї бічної панелі. Як дозволити користувачу активувати її? Спершу, додамо трохи коду графічного інтерфейсу Qt, а саме, створимо кнопку!

Типово, у Krita використано PyQt, але документація з PyQt є доволі низькоякісною, здебільшого через те, що документація Qt є якісною. Часто можна бачити, що документація до класу у PyQt, скажімо, QWidget є блідою копією звичайної документації Qt з відповідного класу.

Що б там не було, спочатку нам слід створити QWidget. Це не складно: під setWindowTitle додайте таке:

mainWidget = QWidget(self)
self.setWidget(mainWidget)

Далі, ми створимо кнопку:

buttonExportDocument = QPushButton("Export Document", mainWidget)

Тепер, для того, щоб з’єднати нашу кнопку з нашою функцією, нам варто ознайомитися із розділом щодо сигналів у документації. У QPushButton немає власних сигналів, але цей об’єкт успадковує 4 сигнали від QAbstractButton, що означає, що ми можемо ними скористатися. Нам потрібен сигнал clicked.

buttonExportDocument.clicked.connect(self.exportDocument)

Якщо тепер перезапустити Krita, ми побачимо нову бічну панелі і кнопку на ній. Натискання кнопки викличе функцію експортування.

Втім, кнопку не вирівняно. Причиною цього є те, що у нашого mainWidget немає компонування. Давайте його швиденько додамо:

mainWidget.setLayout(QVBoxLayout())
mainWidget.layout().addWidget(buttonExportDocument)

У Qt передбачено декілька варіантів компонувань, але найпростішими у користуванні є QHBoxLayout та QVBoxLayout. Використання цих компонувань вирівнює віджети горизонтально або вертикально.

Перезапустіть Krita. Тепер кнопку має бути вирівняно належним чином.

Сигнали і слоти PyQt

Ми вже використовуємо сигнали і слоти PyQt, але іноді виникає потреба у створенні наших власних сигналів та слотів. Оскільки документація PyQt є доволі складною до розуміння, а способи створення сигналів та слотів дуже відрізняються від Qt у C++, ми наведемо тут деякі пояснення:

Усі функції Python, які ви створюєте у PyQt, можна вважати слотами, тобто їх можна з’єднати із сигналами, зокрема Action.triggered або QPushButton.clicked. Втім, у QCheckBox передбачено сигнал для перемикання (toggled), який надсилає булеве значення. Як же зробити так, щоб наша функція приймала це булеве значення?

Спочатку, правильно сформуємо імпортування для створення нетипових слотів:

from PyQt5.QtCore import pyqtSlot

(Якщо у списку імпортування вже є from PyQt5.QtCore import *, цього, звичайно ж, уже не треба робити)

Далі, вам слід додати визначення слоту pyQt перед вашою функцією:

@pyqtSlot(bool)
def myFunction(self, enabled):
    enabledString = "disabled"
    if (enabled == True):
        enabledString = "enabled"
    print("The checkbox is"+enabledString)

Далі, коли вами створено пункт для позначки, ви можете скористатися, наприклад, myCheckbox.toggled.connect(self.myFunction).

Подібним же чином, для створення власних сигналів PyQt слід скористатися таким кодом:

# до змінних учасників класу додаємо назву сигналу
signal_name = pyqtSignal(bool, name='signalName')

def emitMySignal(self):
    # І ось, як ви вмикаєте надсилання сигналу.
    self.signal_name.emit(True)

і використовувати належне імпортування:

from PyQt5.QtCore import pyqtSignal

Для видання та створення слотів для об’єктів, які не є стандартними об’єктами Python достатньо взяти їхні назви у лапки.

Зауваження щодо тестів модулів

Якщо ви хочете написати тести модулів для вашого додатка, ознайомтеся із цим модулем krita.

Висновки

Гаразд, отже ми обговорили усі специфічні для Krita подробиці щодо створення додатків Python. Тут немає пояснень щодо того, як обробляти дані пікселів або як найкраще обробляти документи, але якщо у вас є хоч якийсь досвід роботи з Python, за допомогою нашої документації ви зможете розпочати створення власних додатків.

Як завжди, ретельно знайомтеся з кодом і уважно читайте документацію з програмного інтерфейсу Python, Krita та Qt, щоб краще знати можливості цих інструментів, і ви зможете зробити доволі багато.