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: ~/.qgis2/python/plugins et (qgis_prefix)/share/qgis/python/plugins

  • Windows: ~/.qgis2/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

Vrai

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

email de l’auteur, non affiché dans le gestionnaire de plugins QGIS ou dans le site Web, à moins d’être un utilisateur enregistré logué, donc seulement visible par les autres auteurs de plugin et par les administrateurs du site Web de plugin

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

Vrai

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 (relatif au dossier de base du package compressé du plugin) d’une image web sympa (PNG, JPEG)

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
tracker=http://bugs.itopen.it
repository=http://www.itopen.it/repo
; 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
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

Note

Dans les environnements Windows, tenter de lancer pyrcc4 en mode commande ou depuis Powershell générera probablement une erreur du type “Windows ne peut pas accéder au périphérique, au répertoire, ou au fichier [...]”. La solution la plus simple est certainement d’utiliser l’environnement OSGeo4W mais si vous vous sentez capable de modifier la variable d’environnement PATH ou de préciser de chemin vers l’exécutable de manière explicite vous devriez pouvoir le trouver dans <Votre répertoire d'installation de QGIS>\bin\pyrcc4.exe.

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.

Traduction

En peu d’étapes, vous pouvez configurer un environnement pour la traduction de votre extension, de telle sorte que, selon les paramètres de langue de l’ordinateur, l’extension sera chargée dans différentes langues.

Prérequis logiciels

La façon la plus facile de créer et gérer les fichiers de traduction est d’installer Qt Linguist. Dans un environnement Linux, cela peut se faire en tapant:

sudo apt-get install qt4-dev-tools

Fichiers et répertoire

Une fois l’extension créée, vous verrez un dossier i18n à la racine du dossier de l’extension.

Tous les fichiers de traduction doivent être à l’intérieur de ce répertoire.

Fichier .pro

D’abord, vous devez créer un fichier .pro, qui est un fichier projet que Qt Linguist peut traiter.

Dans ce fichier .pro vous devez préciser tous les fichiers et tous les formulaires que vous voulez traduire. Ce fichier est utilisé pour paramétrer les fichiers et les variables de localisations. Un fichier projet possible, correspondant à la structure de notre :ref:`example plugin<plugin_files_architecture>’:

FORMS = ../form.ui
SOURCES = ../your_plugin.py
TRANSLATIONS = your_plugin_it.ts

Votre extension peut suivre une structure plus complexe, et elle peut être distribuée Si c’est le cas, gardez en tête que pylupdate4, le programme que nous avons utilisé pour lire le fichier .pro et mettre à jour la chaîne de caractères traduisible, ne développe pas les caractères génériques, vous devez donc placer tous les fichiers explicitement dans le fichier `` .pro``. Votre fichier de projet pourrait ressembler à ceci:

FORMS = ../ui/about.ui ../ui/feedback.ui \
        ../ui/main_dialog.ui
SOURCES = ../your_plugin.py ../computation.py \
          ../utils.py

En plus, le fichier your_plugin.py est celui qui appelle tous les menus et sous-menus de votre plugin dans la barre d’outils de QGIS et vous voulez tous les traduire.

Enfin, à l’aide de la variable TRANSLATIONS, vous pouvez spécifier les langues que vous souhaitez en traduction.

Warning

Assurez-vous de nommer le fichier .ts comme combinaison de votre_extension + langue + .ts, sinon, le chargement de la langue échouera! Utilisez un code en 2 lettres pour votre langue (it pour l’Italien; fr pour le Français, etc...)

fichier .ts

Une fois le fichier .pro créé, vous êtes en capacité de générer les fichiers .ts pour les différentes langues de votre extension.

Ouvrez un terminal, allez dans le dossier votre_extension/i18n et saisissez:

pylupdate4 your_plugin.pro

vous devriez voir le(s) fichier(s) votre_extension_langue.ts.

Ouvrir le fichier .ts avec Qt Linguist et commencer à traduire.

fichier .qm

Une fois la traduction de votre extension finie (si certains textes ne sont pas traduits, ils apparaîtront dans la langue originale), vous devez créer le fichier .qm (la version compilée du fichier .ts qui sera utilisée par QGIS).

Ouvrez un terminal, allez dans le dossier votre_extension/i18n et saisissez:

lrelease your_plugin.ts

maintenant, dans le répertoire i18n tu verras les fichiers ton_plugin.qm.

Translate using Makefile

Alternatively you can use the makefile to extract messages from python code and Qt dialogs, if you created your plugin with Plugin Builder. At the beginning of the Makefile there is a LOCALES variable:

LOCALES = en

Add the abbreviation of the language to this variable, for example for Hungarian language:

LOCALES = en hu

Now you can generate or update the hu.ts file (and the en.ts too) from the sources by:

make transup

After this, you have updated .ts file for all languages set in the LOCALES variable. Use Qt4 Linguist to translate the program messages. Finishing the translation the .qm files can be created by the transcompile:

make transcompile

You have to distribute .ts files with your plugin.

Charger le plugin

Afin d’exécuter la version traduite de votre extension, ouvrez QGIS, modifiez la langue (Préférences ‣ Options ‣ Langue) et redémarrez QGIS.

Vous devriez voir votre extension dans la bonne langue.

Warning

Si vous effectuez une modification dans votre extension (nouvelle interface, nouveau menu, etc...) vous devrez à nouveau exécuter les commandes ci-dessus afin de regénérer des versions actualisées des fichiers .ts et .qm.