4 Parte II - O primeiro plugin
4.1 4. Estrutura mínima do pacote
4.1.1 4.1 metadata.txt
O metadata.txt descreve o plugin ao QGIS e ao repositório oficial.
[general]
name=Quick Point Logger
description=Captures map clicks into a temporary point layer.
version=0.1.0
qgisMinimumVersion=3.28
qgisMaximumVersion=4.99
author=Seu Nome
email=nome@example.org
homepage=https://github.com/user/quick-point-logger#readme
repository=https://github.com/user/quick-point-logger
tracker=https://github.com/user/quick-point-logger/issues
license=MIT
category=Vector
tags=point,capture,coordinates,logging
experimental=True
deprecated=False
hasProcessingProvider=False
supportsQt6=True
icon=icons/capture.svg4.1.1.1 Campos essenciais
name: nome público, estável e distinto;description: uma frase curta;about: explicação detalhada, dependências e limitações;version: versão SemVer;qgisMinimumVersioneqgisMaximumVersion;repository,tracker,homepageelicense;supportsQt6=Truequando validado para Qt 6.
O repositório oficial exige documentação mínima, links funcionais, licença compatível, descrição em inglês, ausência de binários e um pacote dentro do limite de tamanho aplicável.
4.1.2 4.2 __init__.py
def classFactory(iface):
"""Return the plugin instance used by QGIS."""
from .plugin import QuickPointLoggerPlugin
return QuickPointLoggerPlugin(iface)Mantenha esta função pequena. A importação dentro de classFactory() reduz efeitos colaterais durante a descoberta do plugin.
4.1.3 4.3 Classe principal
from pathlib import Path
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import QAction
class QuickPointLoggerPlugin:
MENU = "&Quick Point Logger"
def __init__(self, iface):
self.iface = iface
self.action = None
self.icon_path = Path(__file__).parent / "icons" / "capture.svg"
def initGui(self):
self.action = QAction(
QIcon(str(self.icon_path)),
"Quick Point Logger",
self.iface.mainWindow(),
)
self.action.triggered.connect(self.run)
self.iface.addPluginToVectorMenu(self.MENU, self.action)
self.iface.addToolBarIcon(self.action)
def run(self):
self.iface.messageBar().pushInfo(
"Quick Point Logger",
"The plugin is running.",
)
def unload(self):
if self.action is None:
return
self.iface.removePluginVectorMenu(self.MENU, self.action)
self.iface.removeToolBarIcon(self.action)
self.action.deleteLater()
self.action = NoneEste padrão aparece no GPX Batch Converter: initGui() cria QAction, associa triggered ao método run(), adiciona ao menu Vector e à barra; unload() remove os elementos e fecha a interface.
4.1.4 4.4 Ciclo de vida e limpeza
Um plugin que funciona ao activar, mas deixa sinais ou tarefas depois de desactivar, é instável. No unload():
- desassocie ou remova acções;
- cancele tarefas activas;
- aborte respostas de rede;
- remova painéis;
- desactive ferramentas de mapa;
- chame
deleteLater()para widgets Qt; - limpe referências Python.
4.3 6. Interfaces: QDialog e QDockWidget
4.3.1 6.1 Quando usar cada um
Use QDialog para uma tarefa com início e fim, como converter ficheiros. Use QDockWidget para uma ferramenta que acompanha o trabalho no mapa, como um log de captura.
GPX Batch Converter: diálogo com pastas, formatos, opções, progresso e resultados.
GeoClick Capture: painel lateral com sessão, camada, snapping, tabela e exportação.
4.3.2 6.2 Interface programática ou Qt Designer
Interface programática:
- facilita compatibilidade e revisão de código;
- não exige compilar
.ui; - funciona bem para formulários médios.
Qt Designer:
- acelera layouts complexos;
- separa desenho visual;
- exige estratégia para carregar
.uisem ficheiros gerados desnecessários.
O repositório oficial recomenda evitar ficheiros gerados como ui_*.py quando não são necessários. Pode carregar o .ui em tempo de execução.
4.3.3 6.3 Um selector de pasta reutilizável
class FolderSelector(QWidget):
def __init__(self, title, parent=None):
super().__init__(parent)
self.title = title
self.path_edit = QLineEdit()
self.browse_button = QPushButton("Browse...")
self.browse_button.clicked.connect(self.browse)
layout = QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(self.path_edit, 1)
layout.addWidget(self.browse_button)
def browse(self):
selected = QFileDialog.getExistingDirectory(self, self.title)
if selected:
self.path_edit.setText(selected)Transformar um conjunto repetido de widgets numa classe reduz duplicação e facilita validação.
4.3.4 6.4 Widgets QGIS
Sempre que possível, use widgets que conhecem o projecto. Exemplo:
from qgis.core import Qgis, QgsMapLayerProxyModel
from qgis.gui import QgsMapLayerComboBox
self.layer_combo = QgsMapLayerComboBox()
point_filter = getattr(QgsMapLayerProxyModel, "PointLayer", None)
if point_filter is None:
point_filter = Qgis.LayerFilter.PointLayer
self.layer_combo.setFilters(point_filter)
self.layer_combo.setAllowEmptyLayer(True)
self.layer_combo.setShowCrs(True)QgsMapLayerComboBox acompanha camadas adicionadas, removidas e renomeadas. Uma QComboBox manual torna-se rapidamente desactualizada.
4.4 7. Validação de entradas e experiência do utilizador
Valide antes de iniciar trabalho pesado:
- pasta de entrada existe;
- pasta de saída pode ser criada;
- existe pelo menos um ficheiro compatível;
- pelo menos uma camada ou opção foi seleccionada;
- executáveis necessários foram encontrados;
- camada de destino é válida e tem geometria adequada;
- caminhos não são iguais quando isso causa sobrescrita perigosa.
Mensagens de erro devem indicar acção correctiva:
Fraco: Conversion failed.
Melhor: No .gpx files were found in the selected input folder.
Evite caixas modais para cada aviso dentro de um lote. Use log, tabela de resultados e resumo final.