Développer des extensions Python

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.

Les extensions Python sont listées avec les extensions C++ dans le gestionnaire d’extension. Voici les chemins où elles peuvent être situées:

  • UNIX/Mac: ~/.qgis/python/plugins et (qgis_prefix)/share/qgis/python/plugins

  • Windows: ~/.qgis/python/plugins et (qgis_prefix)/python/plugins

Sous Windows, le répertoire Maison (noté ci-dessus par ~) est généralement situé dans un emplacement du type C:\Documents and Settings\(utilisateur) (sous Windows XP et inférieur) ou dans C:\Utilisateurs\(utilisateur). Étant donné que QGIS utilise Python 2.7, les sous-répertoires de ces chemins doivent contenir un fichier __init__.py pour pouvoir les considérer comme des progiciels Python qui peuvent être importés en tant qu’extensions.

Note

En configurant QGIS_PLUGINPATH avec un chemin d’accès vers un répertoire existant, vous pouvez ajouter ce chemin d’accès à la liste des chemins d’accès qui est parcourue pour trouver les extensions.

Étapes :

  1. 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?

  2. Créer des fichiers: Créer les fichiers décrits plus loin. Un point de départ (__init.py__). Remplissez les fichiers Métadonnées de l’extension (metadata.txt). Un corps principal de l’extension (mainplugin.py). Un formulaire créé avec QT-Designer (form.ui), et son fichier de ressources resources.qrc.

  3. Écrire le code: Écrire le code à l’intérieur du fichier mainplugin.py

  4. Test: Fermez et ré-ouvrez QGIS et importez à nouveau votre extension. Vérifiez si tout est OK.

  5. 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.

Écriture d’une extension

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 et vous pourrez 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é !

Fichiers de l’extension

Vous pouvez voir ici la structure du répertoire de notre exemple d’extension

PYTHON_PLUGINS_PATH/
  MyPlugin/
    __init__.py    --> *required*
    mainPlugin.py  --> *required*
    metadata.txt   --> *required*
    resources.qrc  --> *likely useful*
    resources.py   --> *compiled version, likely useful*
    form.ui        --> *likely useful*
    form.py        --> *compiled version, likely useful*

A quoi correspondent ces fichiers?

  • __init__.py = Le point d’entrée de l’extension. Il doit comporter une méthode classFactory() et peut disposer d’un autre code d’initialisation.

  • mainPlugin.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 avec 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. 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 une méthode automatisée en ligne pour créer les fichiers de base (le squelette) d’une classique extension Python sous QGIS.

Il existe également une extension QGIS nommée Plugin Builder qui crée un modèle d’extension depuis QGIS et ne nécessite pas de connexion Internet. C’est l’option recommandée car elle produit des sources compatibles avec la version 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.

Contenu de l’extension

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.

Métadonnées de l’extension

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
name

Vrai

texte court contenant le nom de l’extension

qgisMinimumVersion

Vrai

version minimum de QGIS en notation par points

qgisMaximumVersion

Faux

version maximum de QGIS en notation par points

description

Vrai

un texte court qui décrit l’extension. Le HTML n’est pas autorisé

about

Faux

un texte long qui décrit l’extension en détail, pas de HTML autorisé

version

Vrai

texte court avec le numéro de version par points

author

Vrai

nom de l’auteur

email

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é

experimental

Faux

indicateur booléen, Vrai ou Faux

deprecated

Faux

indicateur booléen, Vrai ou 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

homepage

Faux

une URL valide pointant vers la page d’accueil de l’extension

repository

Faux

une URL valide pour le dépôt du code source

tracker

Faux

une URL valide pour les billets et rapports de bugs

icon

Faux

un nom de fichier ou un chemin relatif (par rapport au dossier racine de l’extension, fichiers compressés)

category

Faux

soit Raster, Vector, Database 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 notre extension) mais elles peuvent également être placées dans les menus Raster, Vecteur, Base de données et Internet.

Une entrée “category” 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 “category” sont : Vector, Raster, Database ou Web. Par exemple, si votre extension sera disponible dans le menu Raster, ajoutez ceci à metadata.txt :

category=Raster

Note

Si la variable qgisMaximumVersion est vide, elle sera automatiquement paramétrée à la version majeure plus .99 lorsque l’extension sera chargée sur le Dépôt officiel des extensions Python.

Un exemple pour ce fichier metadata.txt

; the next section is mandatory

[general]
name=HelloWorld
email=me@example.com
author=Just Me
qgisMinimumVersion=2.0
description=This is an example plugin for greeting the world.
    Multiline is allowed:
    lines starting with spaces belong to the same
    field, in this case to the "description" field.
    HTML formatting is not allowed.
about=This paragraph can contain a detailed description
    of the plugin. Multiline is allowed, HTML is not.
version=version 1.2
; end of mandatory metadata

; start of optional metadata
category=Raster
changelog=The changelog lists the plugin versions
    and their changes as in the example below:
    1.0 - First stable release
    0.9 - All features implemented
    0.8 - First testing release

; Tags are in comma separated value format, spaces are allowed within the
; tag name.
; Tags should be in English language. Please also check for existing tags and
; synonyms before creating a new one.
tags=wkt,raster,hello world

; these metadata can be empty, they will eventually become mandatory.
homepage=http://www.itopen.it
tracker=http://bugs.itopen.it
repository=http://www.itopen.it/repo
icon=icon.png

; experimental flag (applies to the single version)
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

__init__.py

Ce fichier est requis par le système d’import de Python. QGIS impose aussi que ce fichier contienne une fonction classFactory() qui est appelée lorsque l’extension est chargée dans QGIS. Elle reçoit une référence vers une instance de la classe QgisInterface et doit renvoyer l’instance de la classe de l’extension située dans le fichier mainplugin.py. Dans notre cas, elle s’appelle TestPlugin (voir plus loin). Voici à quoi devrait ressembler le fichier __init__.py :

def classFactory(iface):
  from mainPlugin import TestPlugin
  return TestPlugin(iface)

## any other initialisation needed

mainPlugin.py

C’est l’endroit où tout se passe et voici à quoi il devrait ressembler (ex: mainPlugin.py) :

from PyQt4.QtCore import *
from PyQt4.QtGui import *
from qgis.core import *

# initialize Qt resources from file resources.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.setObjectName("testAction")
    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 de l’extension qui doivent exister dans le fichier source principal de l’extension (ex: mainPlugin.py) sont :

  • __init__ –> qui donne accès à l’interface de QGIS

  • initGui() –> appelée lorsque l’extension est chargée.

  • unload() –> chargée lorsque l’extension est déchargée.

Vous pouvez voir que dans l’exemple ci-dessus, la fonction addPluginToMenu() est utilisée. Elle ajoute l’entrée de menu correspondante au menu Extension. Il existe d’autres méthodes pour ajouter l’action dans un menu différent. Voici une liste de ces méthodes :

  • addPluginToRasterMenu()
  • addPluginToVectorMenu()
  • addPluginToDatabaseMenu()
  • addPluginToWebMenu()

Toutes 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.setObjectName("testMenu")
    self.menu.setTitle("MyMenu")

    self.action = QAction(QIcon(":/plugins/testplug/icon.png"), "Test plugin", self.iface.mainWindow())
    self.action.setObjectName("testAction")
    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()

N’oubliez pas de paramétrer la propriété objectName de QAction et de QMenu avec un nom spécifique à votre extension pour qu’elle puisse être personnalisée.

Fichier de ressources

Vous pouvez voir que dans la fonction 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>

Il est bon d’utiliser un préfixe qui n’entrera pas en collision avec d’autres extensions ou toute autre partie de QGIS sinon vous risquez de récupérer des ressources que vous ne voulez pas. Vous devez juste générer un fichier python qui contiendra ces ressources. Cela peut être fait avec la commande pyrcc4.

pyrcc4 -o resources.py resources.qrc

Et c’est tout ! Rien de bien compliqué :)

Si tout a été réalisé correctement, vous devriez pouvoir trouver et charger votre extension dans le gestionnaire d’extensions et voir un message dans la console lorsque l’icône de barre d’outils ou l’entrée de menu appropriée est sélectionnée.

Lorsque vous travaillez sur une extension réelle, il est sage d’écrire l’extension dans un autre répertoire et de créer un fichier makefile qui générera l’interface graphique et les fichiers ressources en terminant par l’installation de l’extension dans l’installation QGIS.

Documentation

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.

La fonction showPluginHelp() prend également les paramètres packageName qui identifie une extension spécifique pour laquelle une aide sera affichée; filename qui peut remplacer “index” dans les noms de fichiers à rechercher; section qui est le nom d’une ancre HTML dans le document où le navigateur doit se positionner.