Krita Python 插件编写教程¶
你可能已经编写了一些可以在 Python 脚本工具中运行的脚本,但你说不定还想进一步扩展其功能,并且让它随 Krita 进程自动启动。把脚本包装成插件不但用起来更方便,功能也更加强大。
尽管你可能已经对 Python 了如指掌,但要想 Krita 正确识别你的插件,学习一些细节知识还是很有必要的。本节将介绍如何创建 Krita 专用的几种 Python 脚本。
本文的范例是为那些对 Python 已经有了初步了解的人们准备的。这些范例和相关的 API 文档鼓励读者大胆地进行实验,而不是盲目地复制粘贴代码,所以请认真阅读它们。
创建 Krita 能够识别的插件¶
Krita 的脚本由两部分组成:脚本的文件夹和脚本的”.desktop” 文件。 脚本的文件夹保存了脚本的 Python 文件,而 desktop 文件则供 Krita 加载和注册该脚本使用。要让 Krita 加载你的脚本,两者必须同时被放置在 Krita 的资源文件夹的 pykrita
子文件夹里面 (不同操作系统的路径不同,请参见 资源管理 页面)。要显示资源文件夹,可在菜单栏打开 对话框。点击 打开资源文件夹 按钮后,系统自带的资源管理器将会显示 Krita 的资源文件夹。具体操作可参考 API 文档 的“Auto starting scripts”一节。如果 Krita 的资源文件夹里没有 pykrita
子文件夹,请使用系统的资源管理器新建一个。
Krita 通过带有 .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
对于 Krita 的 Python 插件,此项必须设为
Krita/PythonPlugin
。- X-KDE-Library
此项指定该插件的文件夹名称。
- X-Python-2-Compatible
指定是否兼容 Python 2。例子中此项为 false (否),如果 Krita 是为 Python 2 而不是 3 进行编译 (cmake 配置参数为
-DENABLE_PYTHON_2=ON
),那么 false 意味着此插件不会在 Krita 里面显示。- X-Krita-Manual
此项为可选参数,它指向该插件配套的使用手册项目。它会被显示在 Python 插件管理器中。 HTML 格式的手册将显示为富文本 ,否则将显示为纯文本。
- Name
插件在 Python 插件管理器中显示的名称。
- Comment
插件在 Python 插件管理器中的描述。
Krita 的 Python 插件必须是 Python 模块,因此每个插件都要包含一个 __init__.py
脚本,该脚本包含下面这行代码:
from .myplugin import *
.myplugin 字串用来指定插件的主程序文件名。按照上面的介绍制作一个插件放到 Krita 的资源文件夹,然后重新启动 Krita,该插件便会被显示在“配置 Krita”对话框的“Python 插件管理器”页面。不过现在它会被显示为灰色,因为插件的文件夹里面并没有一个叫做 myplugin.py 的文件。如果把鼠标悬停在已禁用的插件上,你可以看到相关的错误信息。
备注
你需要自行启用你的插件。前往设置菜单,打开 配置 Krita 对话框,找到 Python 插件管理器页面,然后启用你的插件。
小结¶
总而言之,如果你想要创建一个叫做 myplugin
的脚本:
- 在 Krita 的
resources/pykrita
子文件夹里面 创建一个名为
myplugin
的文件夹创建一个名为
myplugin.desktop
的文件
- 在 Krita 的
- 在
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.
在
myplugin/myplugin.py
文件中编写脚本代码。
创建一个扩展程序¶
扩展程序 是随 Krita 一起启动的简单 Python 脚本。它们通过 Extension (扩展) 程序类进行编写。一个最简单的扩展程序代码如下:
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()))
这段代码并不具备任何功能。一般来说我们会在 createActions 处为 Krita 添加功能,这样我们就可以通过 工具 菜单来访问该脚本了。
首先,让我们创建一个 操作 。我们可以用 Window.createAction() 来实现它。Krita 会为它创建的每个窗口调用 createActions ,并把需要使用的窗口对象传递过去。
示例如下:
def createActions(self, window):
action = window.createAction("myAction", "My Script", "tools/scripts")
- “myAction”
此项指定 Krita 用来查找该操作的唯一 ID。
- “My Script”
此项将作为工具名称显示在工具菜单。
重新启动 Krita 之后,你就会发现在工具菜单的脚本里面多了一个叫做“My Script”的操作了。不过它现在还不能够发挥作用,因为我们还没有把它连接到一个脚本。
现在让我们把它写成一个简单的导出文档脚本。把下面的代码添加到 extension 程序类,确保它位于“Krita.instance()”所在的那行代码前面:
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())
然后为上面代码中调用的 QFileDialog 添加 import 功能:
from krita import *
from PyQt5.QtWidgets import QFileDialog
最后,把该操作连接到新建导出文档:
def createActions(self, window):
action = window.createAction("myAction", "My Script")
action.triggered.connect(self.exportDocument)
这便是一个 信号/信号槽连接 的范例,像 Krita 这样的 Qt 应用程序经常会用到这种连接。我们会在后面介绍如何编写我们自己的信号和信号槽。
重新启动 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
此项填写你在编写该扩展程序时给它命名的唯一 ID。
- icon
此项指定一个可用的图标名称,仅在 KDE Plasma 下面显示,因为 Gnome 和 Windows 用户觉得在菜单里显示图标不美观。
- text
指定在快捷键编辑器中显示的文字。
- whatsThis
此项指定的文字会在 Qt 应用程序调用“这是什么”帮助信息时显示。
- toolTip
指定在鼠标悬停时显示的工具提示文字。
- iconText
指定在工具栏中显示时的替代文字。例如“缩放图像至新尺寸”可以被缩写为“调整图像大小”以节省工具栏空间。
- activationFlags
此项决定该操作是否被禁用。
- activationConditions
此项决定该操作被激活的条件 (例如仅在选区可编辑时激活)。可以参考 相关用例代码 。
- shortcut
指定默认的快捷键。
- isCheckable
指定它是否一个复选框。
- statusTip
指定显示在状态栏的提示文字。
将该文件保存为 myplugin.action
,“myplugin”字串应与你的插件名称相同。要注意的是,action 文件不应该保存在资源文件夹的 pykrita 子文件夹,而应该保存在 actions 子文件夹。(把 Python 插件和 desktop 文件放在 share/pykrita
文件夹,把 action 文件放在 share/actions
文件夹) 重新启动 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))
代码中的 setWindowTitle (设置窗口标题) 一项用于指定该工具面板在 Krita 的工具面板列表中显示的名称。setWindowTitle 一项不可省略,但你无需用它来做任何事情,所以是“pass”。
在 addDockWidgetFactory 里面:
- “myDocker”
把此项替换为你的工具面板的唯一 ID,Krita 使用该 ID 来跟踪此面板。
- DockWidgetFactoryBase.DockRight
此项指定面板的位置,可以是
DockTornOff
、DockTop
、DockBottom
、DockRight
、DockLeft
、DockMinimized
。- MyDocker
把此项替换为你想要添加的工具面板的程序类名称。
如果我们把之前编写的导出文档扩展程序添加到这个工具面板,用户要怎样才能激活它呢?答案当然就是用“按钮”了!要添加一个按钮,我们需要进行一些 Qt GUI 编程。
Krita 默认使用 PyQt,但它的文档非常差劲。这大概是因为常规的 Qt 文档非常完善,而且也把 PyQt 作为程序类包含在内了,所以 PyQt 项目就懒得写自己的文档了。例如 PyQt 文档的 QWidget 章节 看上去就是 常规 Qt 文档 给该程序类编写的文档的翻版。
不管怎样,我们首先要做的是创建一个 QWidget
,这并不特别复杂,在 setWindowTitle
下面添加:
mainWidget = QWidget(self)
self.setWidget(mainWidget)
然后创建一个按钮:
buttonExportDocument = QPushButton("Export Document", mainWidget)
要把这个按钮连接到我们编写的功能,首先要文档里查找它的信号。 QPushButton 并不具备它自己特有的信号,但 Qt 文档提到它从 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.triggered
或者 QPushButton.clicked
这样的信号。但 QCheckBox
有一个开关信号,它发送的是布尔值。我们要怎样才能使功能的信号槽接受布尔值呢?
首先,你要为自定义信号槽指定合适的 import 功能:
from PyQt5.QtCore import pyqtSlot
(当然,如果 from PyQt5.QtCore import *
已经在 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 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)
记得指定合适的 import 功能:
from PyQt5.QtCore import pyqtSignal
在使用非标准 Python 对象的信息和信息槽时,你要把它们的名称放到引号里面。
关于单元测试的提醒¶
如果你打算为你的插件编写单元测试,可参考 mock krita module 页面。
结语¶
以上便是给 Krita 创建一个 Python 插件所必须的全部技术细节。虽然本教程既没有介绍如何解析像素数据,也没有介绍处理文档时的最佳做法,但只要你有一些 Python 的编程经验,创建一个插件应该难不倒你。
还是那句话,认真阅读为 Krita、Qt 和 Python 准备的 API 文档,大胆探索你可以用它们来做些什么,我们相信你一定能够把 Krita 的 Python 功能运用自如,开拓出一片新天地。