7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Takumi Akashiro ひとりAdvent Calendar 2020

Day 2

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

Last updated at Posted at 2020-12-01

この記事は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の中の記事の中で一番実用的かもしれません。


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

ではでは~

7
3
0

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
  3. You can use dark theme
What you can do with signing up
7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?