Krita の Python プラグインを作成する方法

あなたは既に、スクリプター Python ランナーで書いた素敵なスクリプトをいくつか持っているかもしれませんが、例えばそれらを自動で実行させたいかもしれません。スクリプトをプラグインに包み込むことで、スクリプターエディタで実行させるよりも、もっと柔軟で強力にすることができます。

もしあなたが Python を本当によく知っているとしても、Python プラグインを Krita に認識させるための、いくつか細かい事柄があります。ですからこのページは Krita 独自の様々な種類の Python スクリプトを作成するための概要を示します。

これらの小さいチュートリアルは Python の基礎事項を理解している方々に向けて書かれたものです。また、単にコピーペーストをするのではなく、実験を行うことが求められています。ですから文を注意深く読んでください。

Krita にあなたのプラグインを認識させる

Krita のスクリプトには 2 つの構成要素があります -- スクリプトディレクトリ (スクリプトの Python ファイルを格納しています) と、Krita があなたのスクリプトをロード、登録するために用いる一つの ".desktop" ファイルです。Krita がスクリプトをロードするために、両方とも Krita のリソースフォルダのサブディレクトリである pykrita 内に置く必要があります (各オペレーティングシステムごとのリソースフォルダのパスについては リソース管理 を確認してください)。リソースフォルダを探すには、Krita を起動し、設定 ‣ リソースを管理... というメニューアイテムをクリックしてください。ダイアログボックスが開きます。リソースフォルダを開く をクリックしてください。これによってオペレーティングシステムのファイルマネージャがあなたの Krita リソースフォルダを開くはずです。"Auto starting scripts" の下にある API ドキュメントを参照してください。もし 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=私のプラグイン
Comment=私の初めてのプラグイン。
Type

これは常に Service となっているはずです。

ServiceTypes

Python プラグインの場合、これは常に Krita/PythonPlugin となっているはずです。

X-KDE-Library

これは、たった今作成したプラグインのフォルダの名前になっているはずです。

X-Python-2-Compatible

このプラグインが Python 2 と互換性があるかどうか。もし Krita が Python 3 ではなく Python 2 を用いてビルドされた (cmake の設定で -DENABLE_PYTHON_2=ON を指定した) 場合、このプラグインは一覧に現れなくなります。

X-Krita-Manual

マニュアルアイテムを指定するオプション値。これは Python プラグインマネージャの中で表示されます。もしこれが HTML ファイルならば、リッチテキストとして表示されます 。そうでなければ、プレーンテキストとして表示されます。

Name

プラグインの名前。これは Python プラグインマネージャに表示されます。

Comment

プラグインの説明。これは Python プラグインマネージャに表示されます。

Krita の Python プラグインは Python のモジュールである必要があります。したがって以下のようなものを含む、__init__.py スクリプトが存在する必要があります。

from .myplugin import *

ここで、.myplugin はプラグインのメインファイルの名前です。Krita を再起動すると、設定にある Python プラグインマネージャでこれが表示されるはずですが、グレーアウトになっているはずです。なぜなら、どこにも myplugin.py がないからです。無効になっているプラグインの上にカーソルをかざすと、エラーメッセージを見ることができます。

注釈

明示的にプラグインを有効にする必要があります。設定メニューで Krita の設定を変更 というダイアログを開き、Python プラグインマネージャのページに行き、プラグインを有効にしてください。

まとめ

まとめとして、もし myplugin という名前のスクリプトを作りたいなら:

  • Krita の resources/pykrita ディレクトリ内に作成
    • 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=私のプラグイン
    Comment=私の初めてのプラグイン。
    
  • スクリプトを myplugin/myplugin.py ファイルに記述してください。

拡張機能の作成

拡張機能 は、Krita の開始時に実行される、比較的小さな Python スクリプトです。これらは Extension クラスによって作成されます。最小の拡張機能はこのような感じです:

from krita import *

class MyExtension(Extension):

    def __init__(self, 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 は生成されるすべての Window に対して createActions を呼び出し、私達が使う必要がある正しい Window オブジェクトを渡します。

つまり...

def createActions(self, window):
    action = window.createAction("myAction", "私のスクリプト", "tools/scripts")
"myAction"

これは Krita がアクションを探すのに使うため、ユニークな ID に置き換える必要があります。

"私のスクリプト"

これは、Tools Menu で表示されます。

Krita を再起動すれば、"私のスクリプト" という名前のアクションが現れるでしょう。このアクションはまだ何もしません。なぜなら私達はまだそれをスクリプトと結びつけていないからです。

では、ドキュメントをエクスポートする単純なスクリプトを作成しましょう。以下のコードを先程の拡張機能のクラスに追加してください。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", "私のスクリプト")
    action.triggered.connect(self.exportDocument)

これは、Krita のような Qt アプリケーションが頻繁に使用する、シグナル / スロット接続 の例です。すぐ後で、どのようにシグナルやスロットを自作するかについて説明します。

Krita を再起動すれば、あなたのアクションはドキュメントをエクスポートするはずです。

設定可能なキーボードショートカットを作成する

今、あなたの新しいアクションは、設定 ‣ Krita の設定を変更 ‣ Krita の設定を変更 ‣ キーボードショートカット には現れていません。

Krita は様々な理由で、アクションが .action ファイル内に存在する時に限り、アクションを Shortcut Settings に追加します。アクションをショートカットに追加するために必要な action ファイルはこのようなものです:

<?xml version="1.0" encoding="UTF-8"?>
<ActionCollection version="2" name="Scripts">
    <Actions category="Scripts">
        <text>私のスクリプト達</text>

        <Action name="myAction">
        <icon></icon>
        <text>私のスクリプト</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>私のスクリプト達</text>

これによって、ショートカットを追加するために、スクリプトの下に "私のスクリプト達" というサブカテゴリが作成されます。

name

拡張機能のセットアップの中でアクションを作成している場合、これは作ったアクションのユニークな ID にする必要があります。

icon

使用可能なアイコンの名前。これらは KDE Plasma においてのみ表示されます。なぜなら、GNOME や Windows のユーザが格好悪いと文句を言ったからです。

text

ショートカットエディタの中で表示されるテキストです。

whatsThis

Qt アプリケーションが 'ヒント' 、つまりヘルプアクションを呼び出した時に表示されるテキストです。

toolTip

マウスをかざすと表示されるツールチップです。

iconText

ツールバーで表示される文字列。例えば、"画像を新しい大きさにリサイズします" は、スペースを節約するために "画像をリサイズします" と短くすることができるので、そのようにここに書きます。

activationFlags

これは、アクションを無効にするかどうかを決定します。

activationConditions

これは、有効化の条件を決定します (例えば、選択範囲が編集可能な場合のみ)。例については このコード を確認してください。

shortcut

デフォルトのショートカットです。

isCheckable

チェックボックスかどうかを指定します。

statusTip

ステータスバーに表示されるステータスチップです。

このファイルを myplugin.action として保存してください。ただし、myplugin をあなたのプラグインの名前に変更してください。この action ファイルは 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("私のドッカー")

    def canvasChanged(self, canvas):
        pass

Krita.instance().addDockWidgetFactory(DockWidgetFactory("myDocker", DockWidgetFactoryBase.DockRight, MyDocker))

ウィンドウタイトルは、Krita のドッカー一覧でどのように表示させるかを指定します。canvasChanged は常に存在している必要がありますが、何かする必要はありません。ですからここでは単に 'pass' しています。

addDockWidgetFactory に関しては...

"myDocker"

これを、ユニークな ID に置き換えてください。Krita があなたのドッカーを追跡するために利用します。

DockWidgetFactoryBase.DockRight

ドッカーの場所。DockTornOffDockTopDockBottomDockRightDockLeftDockMinimized が利用可能です。

MyDocker

これを、追加したいドッカーのクラス名に置き換えてください。

では、もし拡張機能の章で作成したドキュメントのエクスポート機能をこのドッカーに追加したいとなると、どうやってその機能を有効化できるようにすればいいのでしょうか?まず、なにか Qt の GUI のコードが必要です: ボタンを追加しましょう!

デフォルトで Krita は PyQt を使用していますが、そのドキュメントはかなりひどいものとなっています。通常の Qt のドキュメントは本当に素晴らしいのですが、クラスの PyQt のドキュメント、例えば QWidget のドキュメントが、同じクラスの 通常のドキュメント の奇妙なコピーとなっていることがしばしばあります。

とにかく、まず私達が必要とすることは、QWidget を作ることです。これは特に複雑ではありません。setWindowTitle の下にこれらを追加してください:

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

それから、ボタンを生成します:

buttonExportDocument = QPushButton("ドキュメントをエクスポートする", mainWidget)

今、ボタンを関数と結びつけるために、ドキュメント内にあるシグナルの項を見る必要があります。QPushButton それ自体は特有のシグナルを持っていませんが、ドキュメントには、QAbstractButton から 4 つのシグナルを継承していると書かれています。つまり私達もそれらを使えることを意味します。我々の場合、欲しいのは clicked です。

buttonExportDocument.clicked.connect(self.exportDocument)

Krita を再起動すれば、新しいドッカーが表示され、その中にはボタンがあるでしょう。そのボタンをクリックすればエクスポート関数が呼び出されます。

しかしながら、そのボタンはちょっと変に整列されているようです。なぜなら私達の mainWidget にはレイアウトが存在しないからです。ささっと追加しましょう:

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

Qt はいくつか レイアウト を持っていますが、QHBoxLayout と QVBoxLayout が一番使いやすいです。これらは単に水平あるいは垂直にウィジェットを整列させます。

Krita を再起動すれば、ボタンはきちんと配置されているはずです。

PyQt のシグナルとスロット

もう既に PyQt のシグナルやスロットを使ってきましたが、自分でシグナルやスロットを作成したいという場合もあります。PyQt のドキュメントは理解するのがかなり難しい のと、シグナルやスロットを作る方法が C++ の Qt と非常に異なるため、ここで説明します:

PyQt で作るすべての Python の関数は、スロットとして理解できます。つまり、これらは Action.triggeredQPushButton.clicked のようなシグナルと接続することができます。しかし、QCheckBox はトグルのためのシグナルで、ブール値を送信します。どうすればそのブール値を関数は受け取れるでしょうか?

まず、自作のスロットを作成するために、正しくインポートしましょう:

from PyQt5.QtCore import pyqtSlot

(もちろん、もしインポートの中に from PyQt5.QtCore import * が既にあれば、このようにする必要はありません。)

そして、関数定義の前に PyQt スロットの定義を追加する必要があります:

@pyqtSlot(bool)
def myFunction(self, enabled):
    enabledString = "無効となっています"
    if (enabled == True):
        enabledString = "有効となっています"
    print("チェックボックスは"+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 モジュール を見てください。

結論

オーケー。これで Python プラグインを作ることに関する Krita 特有の詳細について説明してきました。これらはピクセルデータをパースしたり、ドキュメントに関するベストプラクティスではないのですが、Python に対するちょっとした経験があれば、自分自身のプラグインを作り始めることができるはずです。

いつものことですが、コードをじっくり読み、Python や Krita、Qt の API ドキュメントを注意深く読んで、何ができるか確認してください。そうすれば上手くいくでしょう。