目標

QGIS プラグインから他のプラグインを呼び出してみる」を書いていたらプラグインの動的ロードと組み合わせたら色々できるのではないかと閃いたので試してみたいと思います。
ついでに動的ロードしたプラグイン同士をシグナルで繋げて動かしてみよう。

下準備

Githubにテスト用のダミープラグインを用意しました。ダウンロードはこちらです。
QGIS3のプラグインフォルダにaaa_pluginbbb_pluginというフォルダを作成してその中にダウンロードしたソースファイルをコピーしておきます。

Pythonコンソールから動的ロード

QGISの[プラグイン]メニューから[Pythonコンソール]を起動します。
まずは現在読み込まれているプラグインリストを確認してみましょう。

>>> qgis.utils.plugins.keys()
dict_keys(['pluginbuilder3', 'plugin_reloader', 'db_manager', 'processing'])

aaa_pluginbbb_pluginも読み込まれていませんね。
ではaaa_pluginをロードしてみます。

>>> plugin_name = 'aaa_plugin'
>>> qgis.utils.loadPlugin(plugin_name)
True
>>> qgis.utils.startPlugin(plugin_name)
True
>>> qgis.utils.plugins.keys()
dict_keys(['pluginbuilder3', 'plugin_reloader', 'db_manager', 'processing', 'aaa_plugin'])

aaa_pluginがロードされてpluginsに追加されていることが確認できました。
ではaaa_pluginのメソッドを叩いてみましょう。

>>> plugin = qgis.utils.plugins[plugin_name]
>>> plugin.handle_trigger('dynamic_load')

引数に与えた文字列がメッセージダイアログで表示されました。
問題なくプラグインのロードができたようです。

プラグイン内で動的ロード&シグナル

今度はプラグイン(aaa_plugin)内で他のプラグイン(bbb_plugin)をロードして、ロード先からロード元のシグナルにコネクトして、ロード元のシグナルを受けてロード先がメッセージを表示するってのをやってみます。
言葉で書くと訳が判らないのでなんちゃってシーケンス図を貼っておきます。
Dynamic Sequence.png

上で利用したそのまんまですが、ソースはこんな感じです。
カスタムシグナルの使い方は以下の記事を参考にさせていただきました。
PyQt5とpython3によるGUIプログラミング[1]

aaa.py
import os
from PyQt5.QtCore import QSettings, QTranslator, qVersion, QCoreApplication, pyqtSignal, QObject
from PyQt5.QtGui import QIcon
from qgis.utils import plugins, loadPlugin, startPlugin
from PyQt5.QtWidgets import QAction, QMessageBox

# Initialize Qt resources from file resources.py
from .resources import *

class CustomSignal(QObject):
    mySignal = pyqtSignal(str)

class AAA:
    myObject = CustomSignal()
    """QGIS Plugin Implementation."""
    def __init__(self, iface):
        self.iface = iface
        self.plugin_instance = None

    def initGui(self):
        pass

    def unload(self):
        pass

    def run(self):
        pass

    def loadAndExec(self, plugin_name, function_name, function_param):
        plugin_list = plugins.keys()
        if not plugin_name in plugin_list:
            # ロード実行
            try:
              loadPlugin(plugin_name)
              startPlugin(plugin_name)
              self.plugin_instance = plugins[plugin_name]
              # メソッド実行
              eval("self.plugin_instance." + function_name + "('" + function_param + "')")
            except:
                QMessageBox.critical(self.iface.mainWindow(), 'error', "%s error" % plugin_name)

    def connectTrigger(self, plugin_name):
        self.parent_plugin_instance = plugins[plugin_name]
        self.parent_plugin_instance.myObject.mySignal.connect(self.handle_trigger)

    def handle_trigger(self, message):
        QMessageBox.information(self.iface.mainWindow(), 'info', "%s" % message)

    def emitMessage(self, message):
        self.myObject.mySignal.emit(message)

では一旦プラグインの状態をリセットしたいのでQGISを再起動してからPythonコンソールを開き直します。
aaa_pluginをロードしてインスタンスを取得するところまでは同じですが、今度はloadAndExecを実行してbbb_pluginをロードしてconnectTriggerメソッド実行させます。

>>> plugin_name = 'aaa_plugin'
>>> qgis.utils.loadPlugin(plugin_name)
True
>>> qgis.utils.startPlugin(plugin_name)
True
>>> plugin = qgis.utils.plugins[plugin_name]
>>> plugin.loadAndExec('bbb_plugin', 'connectTrigger', plugin_name)

これでbbb_pluginからaaa_pluginにコネクトできました。
aaa_pluginのシグナルを起こしてみましょう。

>>> plugin.emitMessage('test')

引数に与えた文字列がメッセージダイアログで表示されました。
目的達成です。

最後に

良い感じに連携が取れることが確認できました。
QGISを使った小中規模の業務システムならこの仕組みを使うことで柔軟な設計ができそうです。

以上です。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.