Il est possible de créer des extensions dans le langage de programmation Python. Comparé aux extensions classiques développées en C++, celles-ci devraient être plus faciles à écrire, comprendre, maintenir et distribuer du fait du caractère dynamique du langage python.
Dans le gestionnaire d’extensions de QGIS, les extensions Python et C++ sont listées ensemble. Elles sont récupérées depuis les dossiers:
UNIX/Mac:
~/.qgis/python/plugins
et(qgis_prefix)/share/qgis/python/plugins
Windows:
~/.qgis/python/plugins
et(qgis_prefix)/python/plugins
Le dossier Home (noté ci-dessus par ~
) sous Windows est habituellement du genre C:\Documents and Settings\(user)
. Les sous-dossiers à l’intérieur de ces chemins sont considérés comme des paquets Python pouvant être importés dans QGIS comme extensions.
Étapes:
Idée: Avoir une idée de ce que vous souhaitez faire avec votre nouvelle extension. Pourquoi le faites-vous? Quel problème souhaitez-vous résoudre? N’y a-t-il pas déjà une autre extension pour ce problème?
Créer des fichiers: Créer les fichiers décrits plus loin. Un point de départ (__init.py__
). Remplissez les Métadonnées de l’extension (metadata.txt
). Un corps principal de l’extension (plugin.py
). Un formulaire sous QT-Designer (form.ui
), et ses resources.qrc
.
Écrire le code: Écrire le code à l’intérieur du fichier plugin.py
Test: Fermez et ré-ouvrez QGIS et importez à nouveau votre extension. Vérifiez si tout est OK.
Publier: Publiez votre extension dans le dépôt QGIS ou créez votre propre dépôt tel un “arsenal” pour vos “armes SIG” personnelles.
Depuis l’introduction des extensions python dans QGIS, un certain nombre d’extensions est apparu - sur le wiki du Dépôt des Extensions vous trouverez certaines d’entre elles, vous pouvez utiliser leur source pour en savoir plus sur la programmation avec PyQGIS ou pour savoir si vous ne dupliquez pas des efforts de développement. L’équipe QGIS maintient également un Dépôt officiel des extensions Python. Prêt à créer une extension, mais aucune idée de quoi faire ? Le wiki des Idées d’extensions Python liste les souhaits de la communauté !
Voici la structure du dossier de l’extension en exemple:
PYTHON_PLUGINS_PATH/
testplug/
__init__.py
plugin.py
metadata.txt
resources.qrc
resources.py
form.ui
form.py
A quoi correspondent ces fichiers?
__init__.py
= Le point de départ de l’extension. Il est normalement vide.
plugin.py
= Le code principal de l’extension. Contient toutes les informations sur les actions de l’extension et le code principal.
resources.qrc
= Le document .xml créé par QT-Designer. Contient les chemins relatifs vers les ressources des formulaires.
resources.py
= La traduction en Python du fichier resources.qrc décrit ci-dessus.
form.ui
= L’interface graphique créée par QT-Designer.
form.py
= La traduction en Python du fichier form.ui décrit ci-dessus.
metadata.txt
= Requis pour QGIS >= 1.8.0. Contient les informations générales, la version, le nom et d’autres métadonnées utilisées par le site des extensions et l’infrastructure de l’extension. Les métadonnées dans le fichier metadata.txt
sont à privilégier aux méthodes dans le fichier __init__.py
. Si le fichier texte est présent, il est utilisé pour récupérer les valeurs. A partir de QGIS 2.0, les métadonnées du fichier __init__.py
ne seront plus acceptées et le fichier metadata.txt
sera requis.
Vous trouverez ici et là deux façons automatisées pour créer les fichiers de base (le squelette) d’une classique extension Python sous QGIS.
Il existe également une extension appelée Plugin Builder qui crée un modèle d’extension et ne requiert pas de connexion internet. C’est l’option qui vous est recommandée car elle produit des sources compatibles avec QGIS 2.0.
Warning
Si vous projetez de déposer l’extension sur le Dépôt officiel des extensions Python, vous devez vérifier que votre extension respecte certaines règles supplémentaires, requises pour sa Validation
Ici vous pouvez trouver des informations et des exemples sur ce qu’il faut ajouter dans chacun des fichiers de la structure de fichiers décrite ci-dessus.
Tout d’abord, le gestionnaire d’extensions a besoin de récupérer des informations de base sur l’extension par exemple son nom, sa description, etc. Le fichier metadata.txt
est le bon endroit où mettre cette information.
Important
Toutes les métadonnées doivent être encodées en UTF-8.
Nom de la métadonnée |
Requis |
Notes |
---|---|---|
nom |
Vrai |
texte court contenant le nom de l’extension |
qgisMinimumVersion | Vrai |
dotted notation of minimum QGIS version |
qgisMaximumVersion | Faux |
dotted notation of maximum QGIS version |
description | Vrai |
un texte plus long décrivant l’extension, pas de HTML autorisé |
version | Vrai |
short string with the version dotted notation |
auteur |
Vrai |
nom de l’auteur |
Vrai |
e-mail de l’auteur, n’apparaîtra pas sur le site web |
|
changelog | Faux |
texte, peut être multi-lignes, pas de HTML autorisé |
expérimental |
Faux |
indicateur booléen, Vrai ou Faux |
obsolète |
Faux |
indicateur booléen, Vrai or Faux, s’applique à l’extension entière et pas simplement à la version chargée |
tags | Faux |
liste séparée par une virgule, les espaces sont autorisés à l’intérieur des balises individuelles |
page d’accueil |
Faux |
une URL valide pointant vers la page d’accueil de l’extension |
dépôt |
Faux |
une URL valide pour le dépôt du code source |
tracker | Faux |
une URL valide pour les billets et rapports de bugs |
icône |
Faux |
un nom de fichier ou un chemin relatif (par rapport au dossier racine de l’extension, fichiers compressés) |
catégorie |
Faux |
soit Raster, Vecteur, Base de Données ou Web |
Par défaut, les extensions sont placées dans le menu Extension (nous verrons dans la section suivante comment ajouter une entrée de menu pour votre extension) mais ils peuvent aussi être placés dans le menu Raster, Vecteur, Base de donnée ou Web. Une entrée “catégorie” existe dans les métadonnées afin de spécifier cela, pour que l’extension soit classée en conséquence. Cette entrée de métadonnées est utilisée comme astuce pour les utilisateurs et leur dit où (dans quel menu) l’extension peut être trouvée. Les valeurs autorisées pour “catégorie” sont : Vecteur, Raster, Base de données, Web et Couches. Par exemple, si votre extension sera disponible dans le menu Raster, ajoutez ceci à metadata.txt
:
category=Raster
Note
If qgisMaximumVersion is empty, it will be automatically set to the major version plus .99 when uploaded to the Dépôt officiel des extensions Python.
Un exemple de fichier metadata.txt:
; the next section is mandatory
[general]
name=HelloWorld
email=me@example.com
author=Just Me
qgisMinimumVersion=2.0
description=This is a plugin for greeting the
(going multiline) world
version=version 1.2
; end of mandatory metadata
; start of optional metadata
category=Raster
changelog=this is a very
very
very
very
very
very long multiline changelog
; tags are in comma separated value format, spaces are allowed
tags=wkt,raster,hello world
; these metadata can be empty
homepage=http://www.itopen.it
tracker=http://bugs.itopen.it
repository=http://www.itopen.it/repo
icon=icon.png
; experimental flag
experimental=True
; deprecated flag (applies to the whole plugin and not only to the uploaded version)
deprecated=False
; if empty, it will be automatically set to major version + .99
qgisMaximumVersion=2.0
One thing worth mentioning is classFactory()
function which is called
when the plugin gets loaded to QGIS. It receives reference to instance of
QgisInterface and must return instance of your plugin - in our
case it’s called TestPlugin
. This is how should this class look like
(e.g. testplugin.py
):
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from qgis.core import *
# initialize Qt resources from file resouces.py
import resources
class TestPlugin:
def __init__(self, iface):
# save reference to the QGIS interface
self.iface = iface
def initGui(self):
# create action that will start plugin configuration
self.action = QAction(QIcon(":/plugins/testplug/icon.png"), "Test plugin", \
self.iface.mainWindow())
self.action.setWhatsThis("Configuration for test plugin")
self.action.setStatusTip("This is status tip")
QObject.connect(self.action, SIGNAL("triggered()"), self.run)
# add toolbar button and menu item
self.iface.addToolBarIcon(self.action)
self.iface.addPluginToMenu("&Test plugins", self.action)
# connect to signal renderComplete which is emitted when canvas
# rendering is done
QObject.connect(self.iface.mapCanvas(), SIGNAL("renderComplete(QPainter *)"), \
self.renderTest)
def unload(self):
# remove the plugin menu item and icon
self.iface.removePluginMenu("&Test plugins",self.action)
self.iface.removeToolBarIcon(self.action)
# disconnect form signal of the canvas
QObject.disconnect(self.iface.mapCanvas(), SIGNAL("renderComplete(QPainter *)"), \
self.renderTest)
def run(self):
# create and show a configuration dialog or something similar
print "TestPlugin: run called!"
def renderTest(self, painter):
# use painter for drawing to map canvas
print "TestPlugin: renderTest called!"
Les seules fonctions incontournables dans une fonction sont initGui()
et unload()
. Ces fonctions sont appelées lorsque l’extension est chargée ou déchargée.
You can see that in the above example, the addPluginMenu() is used. This will add the corresponding menu action to the Plugins menu. Alternative methods exist to add the action to a different menu. Here is a list of those methods:
Tous ont la même syntaxe que la méthode addPluginToMenu().
Ajouter votre extension dans un des menus prédéfinis est une méthode recommandée pour conserver la cohérence de l’organisation des entrées d’extensions. Toutefois, vous pouvez ajouter votre propre groupe de menus directement à la barre de menus, comme le montre l’exemple suivant:
def initGui(self):
self.menu = QMenu(self.iface.mainWindow())
self.menu.setTitle("MyMenu")
self.action = QAction(QIcon(":/plugins/testplug/icon.png"), "Test plugin", \
self.iface.mainWindow())
self.action.setWhatsThis("Configuration for test plugin")
self.action.setStatusTip("This is status tip")
QObject.connect(self.action, SIGNAL("triggered()"), self.run)
self.menu.addAction(self.action)
menuBar = self.iface.mainWindow().menuBar()
menuBar.insertMenu(self.iface.firstRightStandardMenu().menuAction(), self.menu)
def unload(self):
self.menu.deleteLater()
Vous pouvez voir que dans initGui()
, nous avons utilisé une icône depuis le fichier ressource (appelé resources.qrc
dans notre cas):
<RCC>
<qresource prefix="/plugins/testplug" >
<file>icon.png</file>
</qresource>
</RCC>
It is good to use a prefix that will not collide with other plugins or any parts of QGIS, otherwise you might get resources you did not want. Now you just need to generate a Python file that will contain the resources. It’s done with pyrcc4 command:
pyrcc4 -o resources.py resources.qrc
And that’s all... nothing complicated :) If you’ve done everything correctly you should be able to find and load your plugin in the plugin manager and see a message in console when toolbar icon or appropriate menu item is selected.
When working on a real plugin it’s wise to write the plugin in another (working) directory and create a makefile which will generate UI + resource files and install the plugin to your QGIS installation.
La documentation sur l’extension peut être écrite sous forme de fichiers d’aide HTML. Le module qgis.utils fournit une fonction, showPluginHelp(), qui ouvrira le fichier d’aide dans un navigateur, de la même manière que pour l’aide de QGIS.
La fonction showPluginHelp`() recherche les fichiers d’aide dans le même dossier que le module d’appel. elle recherchera, dans l’ordre, index-ll_cc.html
, index-ll.html
, index-en.html
, index-en_us.html
et index.html
, affichant celui qu’elle trouve en premier. Ici, ll_cc
est pour la locale de QGIS. Ceci permet d’inclure des traductions multiples dans la documentation de l’extension.
The showPluginHelp() function can also take parameters packageName, which identifies a specific plugin for which the help will be displayed, filename, which can replace “index” in the names of files being searched, and section, which is the name of an html anchor tag in the document on which the browser will be positioned.