Como criar um “plugin” do Krita em Python

Poderá ter alguns programas interessantes que tenha criado no módulo de execução do Programador de Python, mas provavelmente poderá querer fazer mais com eles e executá-los automaticamente, por exemplo. Se envolver o seu programa num “plugin” poderá obter muito mais flexibilidade do que se executar programas no editor do Programador.

Ok, por isso mesmo que conheça o Python realmente bem, existem alguns pequenos detalhes para ter o Krita a reconhecer um “plugin” em Python. Como tal, esta página irá dar uma antevisão sobre como criar os diversos tipos de programas em Python únicos no Krita.

Estes mini-tutoriais são criados para pessoas com uma compreensão básica do Python, e de forma a encorajar as experiências em vez de simplesmente copiar e colar o código; por isso, leia o texto com cuidado.

Ter o Krita a reconhecer o seu “plugin”

Um programa no Krita tem dois componentes – a pasta do programa (que contém os ficheiros em Python do seu programa) e um ficheiro «.desktop» que o Krita usa para carregar e registar o seu programa. Para o Krita carregar o seu programa, ambos têm de estar na sub-pasta pykrita da sua pasta de recursos do Krita (ver em Gestão de Recursos as localizações por sistema operativo). Para descobrir a sua pasta de recursos, inicie o Krita e carregue na opção do menu Configuração ‣ Gerir os Recursos…. Isto irá abrir uma janela. Carregue no botão Abrir a Pasta de Recursos. Isto deverá abrir um gestor de ficheiros no seu sistema, na sua pasta de recursos do Krita. Veja a documentação da API em «Auto-iniciar os programas». Se não existir nenhuma sub-pasta “pykrita” na pasta de recursos do Krita, use o seu gestor de ficheiros para criar uma.

Os programas são identificados por um ficheiro que termina numa extensão .desktop, que contém informações sobre o programa em si.

Como tal, para cada tipo de “plugin” adequado, terá de criar uma pasta e um ficheiro .desktop.

O ficheiro .desktop deverá parecer o seguinte:

[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=omeuplugin
X-Python-2-Compatible=false
X-Krita-Manual=manualMeuPlugin.html
Name=O Meu Próprio Plugin
Comment=O nosso próprio 'plugin'.
Type

Este deverá ter sempre o valor “service”.

ServiceTypes

Este deverá ser sempre Krita/PythonPlugin para os “plugins” em Python.

X-KDE-Library

Este deverá ser o nome da pasta do “plugin” que acabou de criar.

X-Python-2-Compatible

Se o mesmo é compatível com o Python 2. Se o Krita tiver sido compilado com o Python 2 em vez do 3 (-DENABLE_PYTHON_2=ON na configuração do CMake), então este “plugin” não irá aparecer na lista.

X-Krita-Manual

Um valor opcional que irá apontar para o item do manual. Isto aparece no gestor de “Plugins” em Python. Se for um ficheiro HTML, será apresentado como texto formatado; caso contrário, será apresentado como texto simples.

Name

O nome que irá aparecer no Gestor de “Plugins” em Python.

Comment

A descrição que irá aparecer no Gestor de “Plugins” em Python.

Os “plugins” do Krita em Python precisam de ser módulos de Python; como tal, certifique-se que existe um programa __init__.py, que contenha algo do género…

from .omeuplugin import *

Onde o “.meuplugin” é o nome do ficheiro principal do seu “plugin”. Se reiniciar o Krita, agora deverá ver isto no Gestor de “Plugins” em Python na configuração, mas aparecerá a cinzento, porque não existe nenhum “meuplugin.py”. Se passar o cursor sobre os “plugins” desactivados, poderá ver o que se passa de errado com eles.

Nota

Tem de activar explicitamente o seu “plugin”. Vá ao menu de Configuração, abra a janela para Configurar o Krita… e vá ao Gestor de “Plugins” em Python e active o seu “plugin”.

Resumo

Em resumo, se quiser criar um programa chamado meuplugin:

  • na sua pasta resources/pykrita do Krita, crie
    • uma pasta chamada meuplugin

    • um ficheiro chamado meuplugin.desktop

  • na pasta meuplugin, crie
    • um ficheiro chamado __init__.py

    • um ficheiro chamado meuplugin.py

  • no ficheiro __init__.py coloque este código:

from .omeuplugin import *
  • no ficheiro .desktop coloque este código:

    [Desktop Entry]
    Type=Service
    ServiceTypes=Krita/PythonPlugin
    X-KDE-Library=omeuplugin
    X-Python-2-Compatible=false
    Name=O Meu Próprio Plugin
    Comment=O nosso próprio 'plugin'.
    
  • grave o seu programa no ficheiro meuplugin/meuplugin.py.

Criar uma extensão

As extensões são programas em Python relativamente simples que executam no arranque do Python. São criados através da herança da classe Extension, sendo que o código de extensão mais básico parece-se algo com o seguinte:

from krita import *

class MinhaExtensao(Extension):

    def __init__(self, pai):
        # Isto serve para inicializar o pai, o que é importante nas sub-classes
        super().__init__(pai)

    def setup(self):
        pass

    def createActions(self, janela):
        pass

# E adicione a extensão à lista de extensões do Krita:
Krita.instance().addExtension(MinhaExtensao(Krita.instance()))

Este código obviamente não faz nada. Tipicamente, no “createActions” iremos adicionar acções ao Krita; por isso, podemos aceder ao nosso programa a partir do menu Ferramentas.

Primeiro, vamos criar uma acção. Podemos fazer isto facilmente com o Window.createAction(). O Krita irá invocar o “createActions” para todas as janelas que sejam criadas e passar o objecto Window respectivo que temos de usar.

Assim…

def createActions(self, window):
    action = window.createAction("minhaAccao", "O Meu Programa", "tools/scripts")
«minhaAccao»

Esta deverá ser substituída por um ID único que o Krita irá usar para encontrar a acção.

«O Meu Programa»

Isto é o que ficará visível no O Menu Ferramentas.

se agora reiniciar o Krita, terá uma acção chamada «O Meu Programa». Ela continua a não fazer nada, porque ainda não a associámos a nenhum programa.

Sendo assim, vamos criar um programa simples de exportação do documento. Adicione o seguinte à classe da extensão, certificando-se que é acima onde adiciona a extensão ao Krita:

def exportaDocumento(self):
    # Obter o documento:
    doc =  Krita.instance().activeDocument()
    # A gravação de um documento inexistente provoca estoiros, pelo que convém verificar isso primeiro.
    if doc is not None:
        # Isto invoca a janela de gravação. A mesma devolve um tuplo.
        nomeFicheiro = QFileDialog.getSaveFileName()[0]
        # E exportar o documento para a localização em 'nomeFicheiro'.
        # O InfoObject é um dicionário com opções de exportação específicas, mas quando criamos um vazio, o Krita irá usar os valores por omissão na exportação.
        doc.exportImage(nomeFicheiro, InfoObject())

E adicione a importação para o QFileDialog com as importações:

from krita import *
from PyQt5.QtWidgets import QFileDialog

Depois, para associar a acção à nova exportação de documento:

def createActions(self, janela):
    accao = janela.createAction("minhaAccao", "O Meu Programa")
    accao.triggered.connect(self.exportDocument)

Este é um exemplo de uma ligação signal/slot, que as aplicações do Qt, como o Krita, usam com frequência. Iremos ver como criar os nossos “signals” e “slots” mais tarde.

Reinicie o Krita, para que a sua nova acção já consiga em princípio exportar o documento.

Criar atalhos de teclado configuráveis

Agora, a sua nova acção não aparece em Configuração ‣ Configurar o Krita ‣ Atalhos do Teclado.

O Krita, por diversas razões, só adiciona as acções ao Configuração dos Atalhos quando estão presentes num ficheiro .action. O ficheiro .action necessário para ter a nossa acção adicionadas aos atalhos dever-se-á parecer com o seguinte:

<?xml version="1.0" encoding="UTF-8"?>
<ActionCollection version="2" name="Scripts">
    <Actions category="Scripts">
        <text>Os Meus Programas</text>

        <Action name="minhaAccao">
        <icon></icon>
        <text>O Meu Programa</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>Os Meus Programas</text>

Isto irá criar uma sub-categoria nos programas chamada «Os Meus Programas», onde poderá adicionar os seus atalhos.

name

Este deverá ser o ID único que criou para a sua acção, quando a criou na configuração da extensão.

icon

o nome de um possível ícone. Isto só irá aparecer no Plasma do KDE, dado que os utilizadores do Gnome e do Windows se queixaram que pareciam feios.

text

O texto que irá mostrar no editor de atalhos.

whatsThis

O texto que aparece quando uma aplicação do Qt invoca de forma específica a acção “o que é isto?”, que é uma acção de ajuda.

toolTip

A dica que irá aparecer à passagem do rato.

iconText

O texto que irá mostrar quando aparecer numa barra de ferramentas. Por exemplo, o «Dimensionar a Imagem para um Novo Tamanho» poderá ser reduzido a «Dimensionar a Imagem» para poupar espaço, daí estar aqui.

activationFlags

Isto define se uma acção está desactivada ou não.

activationConditions

Isto define as condições de activação (p.ex., activar apenas quando a selecção estiver editável). Veja o código para ver alguns exemplos.

shortcut

A combinação de teclas por omissão.

isCheckable

Se é uma opção de marcação ou não.

statusTip

A dica de estado que será apresentada numa barra de estado.

Grave isto como meuplugin.action, onde o meuplugin é o nome do seu plugin. O ficheiro .action deverá ser gravado não na pasta de recursos pykrita, mas sim numa pasta de recursos chamada «actions». (Como tal, o share/pykrita é onde ficam os ficheiros dos plugins em Python e os ficheiros .desktop, e o share/actions é onde se colocam os ficheiros .action). Reinicie o Krita. O atalho agora deverá aparecer na lista de acções de atalho.

Criar uma área acoplável

A criação de uma área acoplável personalizada é muito semelhante a criar uma extensão. As áreas acopláveis são de certa forma um pouco mais simples, mas também necessitam mais do uso de elementos gráficos. Isto é o código básico de uma área acoplável:

from PyQt5.QtWidgets import *
from krita import *

class MinhaArea(DockWidget):

    def __init__(self):
        super().__init__()
        self.setWindowTitle("A Minha Área")

    def canvasChanged(self, canvas):
        pass

Krita.instance().addDockWidgetFactory(DockWidgetFactory("minhaArea", DockWidgetFactoryBase.DockRight, MinhaArea))

O título da janela é como irá aparecer na lista de áreas acopláveis no Krita. O “canvasChanged” precisa precisa sempre de estar presente, mas não tem de fazer nada com ele, daí bastar um “pass”.

Para o “addDockWidgetFactory”…

«minhaArea»

Substitua isto por um ID único para a sua área acoplável, o qual será usado pelo Krita para manter o registo dela.

DockWidgetFactoryBase.DockRight

A localização. Poderá assumir um dos valores DockTornOff, DockTop, DockBottom, DockRight, DockLeft ou DockMinimized

MinhaArea

Substitua isto pelo nome da classe da área acoplável que deseja adicionar.

Por isso, se adicionarmos a função de exportação de documentos que criámos na secção da extensão a este código da área acoplável, como é que permitimos ao utilizador que o active? Primeiro, teremos de fazer algum código de GUI no Qt: Vamos adicionar um botão!

Por omissão, o Krita usa o PyQt, mas a sua documentação é bastante má, principalmente porque a documentação do Qt normal é bastante boa; irá também perceber com frequência que a documentação do PyQt para uma dada classe, como por exemplo a QWidget é uma espécie de cópia estranha da documentação do Qt normal para essa classe.

De qualquer forma, o que precisamos de fazer primeiro é criar um QWidget; não é muito complicado - em setWindowTitle, adicione:

janelaPrincipal = QWidget(self)
self.setWidget(janelaPrincipal)

Depois criamos um botão:

botaoExportarDocumento = QPushButton("Exportar o Documento", janelaPrincipal)

Agora, para associar o botão à nossa função, teremos de olhar para os “signals” na documentação. O QPushButton não tem “signals” únicos por si só, mas diz que herda 4 “signals” do QAbstractButton, o que significa que também poderemos usar esses. No nosso caso, queremos o “clicked”.

botaoExportarDocumento.clicked.connect(self.exportarDocumento)

Se agora reiniciarmos o Krita, iremos ter uma nova área acoplável e, nessa área acoplável, existe um botão. Se carregar no botão, irá invocar a função de exportação.

Contudo, o botão parece um bocado mal alinhado. Isto é porque o nosso mainWidget não tem nenhum “layout” (disposição). Vamos tratar já disso:

janelaPrincipal.setLayout(QVBoxLayout())
janelaPrincipal.layout().addWidget(botaoExportarDocumento)

O Qt tem diversas disposições, mas a QHBoxLayout e a QVBoxLayout são as mais simples de usar, já que organizam simplesmente os elementos na horizontal ou na vertical.

Reinicie o Krita, para que o botão fique agora colocado de forma mais agradável.

“Signals” e “Slots” do PyQt

Já temos estado a usar os “signals” e “slots” do PyQt anteriormente, mas existem alturas em que poderá querer criar os seus próprios “signals” e “slots”. Como a documentação do PyQt é relativamente complicada de perceber, e a forma como os “signals” e “slots” são criados é muito diferente do Qt em C++, vamos explicar isso aqui:

Todas as funções de Python que criar no PyQt podem ser entendidas como “slots”, o que significa que poderá estabelecer uma ligação com “signals”, como por exemplo o Action.triggered ou o QPushButton.clicked. Contudo, o QCheckBox tem um “signal” para “toggled”, o qual recebe um booleano. Como é que fazemos com que a nossa função aceite esse booleano?

Primeiro, certifique-se que tem a importação correcta para criar “slots” personalizados:

from PyQt5.QtCore import pyqtSlot

(Se já existir um from PyQt5.QtCore import * na lista de importações, então não terá de fazer isto, como é óbvio.)

Depois, precisa de adicionar uma definição de “slot” do PyQt antes da sua função:

@pyqtSlot(bool)
def minhaFuncao(self, activo):
    textoActivo = "desactivada"
    if (activo == True):
        textoActivo = "activada"
    print("A opção está "+textoActivo)

Depois, quando tiver criado a sua opção de marcação, poderá fazer algo do tipo “minhaOpcao.toggled.connect(self.minhaFuncao)”.

De forma semelhante, para criar os seus próprios “signals” do PyQt, poderá fazer o seguinte:

# o nome do sinal é adicionado às variáveis-membros da classe
nome_sinal = pyqtSignal(bool, name='nomeSinal')
def emitirOMeuSinal(self):
    # E assim é como desencadeia o sinal a emitir.
    self.nome_sinal.emit(True)

E usar a importação correcta:

from PyQt5.QtCore import pyqtSignal

Para emitir ou criar “slots” para objectos que não sejam objectos de Python normais, só terá de colocar os seus nomes entre aspas.

Um anota sobre testes unitários

Se quiser criar testes unitários para o seu “plugin”, dê uma vista de olhos sobre o módulo “krita” de testes.

Conclusão

Ok, com isto cobrimos todos os detalhes específicos para criar “plugins” em Python. Não define como processar os dados dos pixels, ou as melhores práticas com os documentos, mas tiver alguma experiência com o Python, deverá ser capaz de começar a criar os seus próprios “plugins”.

Como sempre, leia o código com cuidado e leia a documentação da API em Python, do Krita e do Qt com cuidado para ver o que é possível, e já terá chegado bastante longe.