この記事はTakumi Akashiro ひとり Advent Calendar 2020の2日目の記事になります。Yah!
前置き
どうも!皆さんは最近、PySide2を触ってますか?
あ、この質問、昨日の記事でもしましたね!
茶番はさておき、今日はホストアプリケーション、
すなわちPythonの処理をQtWebEngineのブラウザ側から呼び出す方法について説明いたします。
QWebChannel
本日使うのはこれ、QWebChannel!
QWebChannelはQtWebWidgetsにあるクラスです。
QWebChannelにQObjectに登録し、QWebEngine側に紐づけ、
ブラウザ側でQWebChannel.jsを使うことで
QWebChannelに登録していたQObjectの処理を呼び出すことが出来ます。
……字面だと分かりづらいですね。
シーケンス図で書くとざっくりこんな感じです。
こんな図見るより、コード読めば一発ですよ一発!
試してみた
#!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_())
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()
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()
締め
というわけで、今回紹介した技術の評価としては以下の通りですね。
評価ラベル | ランク(5段階) |
---|---|
おすすめ度 | ★★★★ |
難易度 | ★★★ |
ニッチ | ★★★★ |
汎用性 | ★★★ |
キュート度 | ★★★★★ |
Maya, Houdini共に割と簡単に使えたので、個人的にはおすすめ度が高いですね。
おそらく今回のAdventCalenderの中の記事の中で一番実用的かもしれません。
記事自体はスクリプトと画像でお茶を濁した感じですが、
使った感じ将来性がありそうな気がしたので、皆様もぜひ活用してみてください!
ではでは~