search
LoginSignup
3

More than 1 year has passed since last update.

posted at

updated at

QtWebEngineのWebページからホスト側の処理を呼び出す

この記事はTakumi Akashiro ひとり Advent Calendar 2020の2日目の記事になります。Yah!

前置き

どうも!皆さんは最近、PySide2を触ってますか?

あ、この質問、昨日の記事でもしましたね!

茶番はさておき、今日はホストアプリケーション、
すなわちPythonの処理をQtWebEngineのブラウザ側から呼び出す方法について説明いたします。

QWebChannel

本日使うのはこれ、QWebChannel!

QWebChannelはQtWebWidgetsにあるクラスです。

QWebChannelにQObjectに登録し、QWebEngine側に紐づけ、
ブラウザ側でQWebChannel.jsを使うことで
QWebChannelに登録していたQObjectの処理を呼び出すことが出来ます。

……字面だと分かりづらいですね。
シーケンス図で書くとざっくりこんな感じです。

image.png

こんな図見るより、コード読めば一発ですよ一発!

試してみた

#!python3
# encoding:utf-8

from PySide2 import QtCore
from PySide2 import QtWidgets
from PySide2 import QtWebEngineWidgets
from PySide2 import QtWebChannel

html_text = """
<!DOCTYPE html>
<html>
    <head>
        <!--Bootstrap (Option)-->
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
        <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
        <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
        <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
        <!--QWebChannel-->
        <script src="qrc:///qtwebchannel/qwebchannel.js"></script>

        <script>
            window.onload = function() {
                new QWebChannel(qt.webChannelTransport, function(channel) {
                    window.manager = channel.objects.web_manager;

                    var $element = document.getElementById("HelloWorld_btn");
                    $element.onclick = function(e) {
                        manager.print_f(e.target.innerText);
                    };

                    var $element = document.getElementById("Number_input");
                    $element.onchange = function(e) {
                        manager.set_number(e.target.value);
                    };
                });
            }
        </script>
    </head>
    <body>
        <button class="btn w-100 btn-primary" id="HelloWorld_btn">Hello World!</button>
        <input class="form-control w-25" type="number" value="0" id="Number_input">
    </body>
</html>
"""


class WebDemo(QtWidgets.QWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.setWindowTitle('Web Demo')

        hbox = QtWidgets.QHBoxLayout()

        self.webview = QtWebEngineWidgets.QWebEngineView()

        page = self.webview.page()

        self.channel = QtWebChannel.QWebChannel()
        web_manager = WebManager(self)
        self.channel.registerObject("web_manager", web_manager)
        page.setWebChannel(self.channel)

        page.setHtml(html_text)

        hbox.addWidget(self.webview)
        self.setLayout(hbox)


class WebManager(QtCore.QObject):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.number_ = 0

    @QtCore.Slot(str)
    def print_f(self, text):
        print(f"called: print_f() / text: {text} / number_: {self.number_}")

    @QtCore.Slot(int)
    def set_number(self, num):
        print(f"called: set_number() / number_: {self.number_}")
        self.number_ = num


if __name__ == "__main__":
    app = QtWidgets.QApplication()
    webdemo = WebDemo()
    webdemo.show()

    exit(app.exec_())

output.gif

Mayaでも試してみた


#!python2
# encoding:utf-8

from PySide2 import QtCore
from PySide2 import QtWidgets
from PySide2 import QtWebEngineWidgets
from PySide2 import QtWebChannel

from maya import cmds
from maya import OpenMayaUI

from shiboken2 import wrapInstance

mayaMainWindowPtr = OpenMayaUI.MQtUtil.mainWindow()
mayaMainWindow = wrapInstance(long(mayaMainWindowPtr), QtWidgets.QWidget)

html_text = """
<!DOCTYPE html>
<html>
    <head>
        <!--Bootstrap (Option)-->
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
        <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
        <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
        <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
        <!--QWebChannel-->
        <script src="qrc:///qtwebchannel/qwebchannel.js"></script>
        <script>
            window.onload = function() {
                new QWebChannel(qt.webChannelTransport, function(channel) {
                    window.manager = channel.objects.web_manager;
                    var $element = document.getElementById("polycube_btn");
                    $element.onclick = function(e) {
                        var callback_print = function(nodename) {
                            var select_btn = document.getElementById("select_btn");
                            select_btn.removeAttribute("disabled");
                            select_btn.value = nodename;
                            select_btn.innerText = "Select: " + nodename
                        };
                        manager.gen_polycube(callback_print);
                    };
                    var $element = document.getElementById("select_btn");
                    $element.onclick = function(e) {
                        var node = e.target.value;
                        manager.select_node(node);
                    };
                    var $element = document.getElementById("select_clear_btn");
                    $element.onclick = function() {
                        manager.select_node("");
                    };
                });
            }
        </script>
    </head>
    <body class="m-3">
        <div class="container">
            <div class="row">
                <button class="col btn w-25 btn-primary m-1" id="polycube_btn">polycube!</button>
                <button class="col btn w-25 btn-secondary m-1" id="select_clear_btn">Select Clear</button>
                <button class="col btn w-25 btn-primary m-1" disabled id="select_btn">Select</button>
            </div>
        </div>
    </body>
</html>
"""


class MayaWebDemo(QtWidgets.QWidget):
    def __init__(self, *args, **kwargs):
        super(MayaWebDemo, self).__init__(*args, **kwargs)

        self.setWindowFlags(QtCore.Qt.Window)
        self.setWindowTitle('Maya Web Demo')

        hbox = QtWidgets.QHBoxLayout()

        self.webview = QtWebEngineWidgets.QWebEngineView()

        page = self.webview.page()
        self.channel = QtWebChannel.QWebChannel()
        web_manager = MayaWebManager(self)
        self.channel.registerObject("web_manager", web_manager)
        page.setWebChannel(self.channel)

        page.setHtml(html_text)

        hbox.addWidget(self.webview)
        self.setLayout(hbox)


class MayaWebManager(QtCore.QObject):
    def __init__(self, *args, **kwargs):
        super(MayaWebManager, self).__init__(*args, **kwargs)
        self.text_ = ""

    @QtCore.Slot(result=str)
    def gen_polycube(self):
        result = cmds.polyCube()[0]
        return result

    @QtCore.Slot(str)
    def select_node(self, name):
        if name:
            cmds.select(name)
        else:
            cmds.select(cl=True)


if __name__ == "__main__":
    demo = MayaWebDemo(mayaMainWindow)
    demo.show()

output_maya.gif

Houdiniでも試してみた

#!python2
# encoding:utf-8

from PySide2 import QtCore
from PySide2 import QtWidgets
from PySide2 import QtWebEngineWidgets
from PySide2 import QtWebChannel

html_text = """
<!DOCTYPE html>
<html>
    <head>
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
        <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
        <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
        <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
        <script src=" qrc:///qtwebchannel/qwebchannel.js"></script>

        <script>
            window.onload = function() {
                new QWebChannel(qt.webChannelTransport, function(channel) {
                    window.manager = channel.objects.manager;

                    var $element = document.getElementById("create_box");
                    $element.onclick = function() {
                        manager.create_box();
                    };
                });
            }
        </script>

    </head>
    <body>
        <button class="btn w-100 btn-primary" id="create_box">create_box</button>
    </body>
</html>
"""

class HoudiniWebDemo(QtWidgets.QWidget):
    def __init__(self, *args, **kwargs):
        super(HoudiniWebDemo, self).__init__(*args, **kwargs)

        self.setGeometry(500, 300, 250, 110)
        self.setWindowTitle('Houdini Web Demo')

        hbox = QtWidgets.QHBoxLayout()

        self.webview = QtWebEngineWidgets.QWebEngineView()

        page = self.webview.page()
        manager = HoudiniWebManager(self)
        self.channel = QtWebChannel.QWebChannel()

        self.channel.registerObject("manager", manager)

        page.setWebChannel(self.channel)
        page.setHtml(html_text)


        hbox.addWidget(self.webview)

        self.setLayout(hbox)

class HoudiniWebManager(QtCore.QObject):
    def __init__(self, *args, **kwargs):
        super(HoudiniWebManager, self).__init__(*args, **kwargs)

    @QtCore.Slot()
    def create_box(self):
        geo = hou.node('/obj/').createNode('geo', 'geo1')
        geo.createNode('box', 'box1')
        geo.moveToGoodPosition()


demo = HoudiniWebDemo()
demo.show()

output2.gif

締め

というわけで、今回紹介した技術の評価としては以下の通りですね。

評価ラベル ランク(5段階)
おすすめ度 ★★★★
難易度 ★★★
ニッチ ★★★★
汎用性 ★★★
キュート度 ★★★★★

Maya, Houdini共に割と簡単に使えたので、個人的にはおすすめ度が高いですね。
おそらく今回のAdventCalenderの中の記事の中で一番実用的かもしれません。


記事自体はスクリプトと画像でお茶を濁した感じですが、
使った感じ将来性がありそうな気がしたので、皆様もぜひ活用してみてください!

ではでは~

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
3