from .myplugin import *
.myplugin 字串用来指定插件的主程序文件名。按照上面的介绍制作一个插件放到 Krita 的资源文件夹,然后重新启动 Krita,该插件便会被显示在“配置 Krita”对话框的“Python 插件管理器”页面。不过现在它会被显示为灰色,因为插件的文件夹里面并没有一个叫做 myplugin.py 的文件。如果把鼠标悬停在已禁用的插件上,你可以看到相关的错误信息。
你需要自行启用你的插件。前往设置菜单,打开 配置 Krita 对话框,找到 Python 插件管理器页面,然后启用你的插件。
总而言之,如果你想要创建一个叫做 myplugin
的脚本:
在 Krita 的 resources/pykrita
子文件夹里面
创建一个名为 myplugin
的文件夹
创建一个名为 myplugin.desktop
的文件
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):
def createActions(self, window):
# 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):
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 功能运用自如,开拓出一片新天地。
修订版本 ad497dc
。