Hi all,
I’m sure you’ve seen the Nemesis#13’s code for running WX and Panda together with multiprocessing (and if you haven’t, you should go check it out here: [Multiprocess wxPython + Panda3D)).
What Nemesis did was really cool (even tho it feels so simple and obvious now, doesn’t it?), but for some reason I really don’t like WX (don’t ask why, it might well be completely irrational ). Thus, I thought about doing the same with other libraries.
I started (successfully) with GTK, but it has the disadvantage of not being cross platform, or at least not making it easy since GTK3. So I decided to take Qt and PySide for a test drive with Nemesis’ ways and here’s the result for those of you who, like me, prefer Qt:
import sys
import platform
from PySide.QtCore import *
from PySide.QtGui import *
from multiprocessing import Process, Pipe
from panda3d.core import loadPrcFileData
from panda3d.core import WindowProperties
from direct.showbase.ShowBase import ShowBase
class FrontEnd(QWidget):
def __init__(self, pipe):
QWidget.__init__(self)
self.viewportWidget = QWidget()
self.testButton = QPushButton("Test layout")
self.testLayout = QVBoxLayout()
self.testLayout.addWidget(self.viewportWidget)
self.testLayout.addWidget(self.testButton)
self.setLayout(self.testLayout)
self.pipe = pipe
self.resize(800, 600)
self.setWindowTitle('Simple')
self.show()
def getViewportID(self):
wid = self.viewportWidget.winId()
if platform.system() == "Windows":
import ctypes
ctypes.pythonapi.PyCObject_AsVoidPtr.restype = ctypes.c_void_p
ctypes.pythonapi.PyCObject_AsVoidPtr.argtypes = [ ctypes.py_object ]
wid = ctypes.pythonapi.PyCObject_AsVoidPtr(wid)
return wid
def resizeEvent(self, e):
self.pipe.send(["resize", e.size().width(), e.size().height()])
class BackEnd(object):
def __init__(self, viewportID, pipe):
self.pipe = pipe
loadPrcFileData("", "window-type none")
ShowBase()
wp = WindowProperties()
wp.setOrigin(0, 0)
wp.setSize(800, 600)
wp.setParentWindow(viewportID)
base.openDefaultWindow(props = wp, gsg = None)
s = loader.loadModel("smiley.egg")
s.reparentTo(render)
s.setY(5)
base.setFrameRateMeter(True)
base.taskMgr.add(self.checkPipe, "check pipe")
run()
def closeCallback(self):
sys.exit()
def resizeCallback(self, viewportSizeH, viewportSizeV):
wp = WindowProperties()
wp.setOrigin(0, 0)
wp.setSize(viewportSizeH, viewportSizeV)
base.win.requestProperties(wp)
def checkPipe(self, task):
while self.pipe.poll():
request = self.pipe.recv()
method = request[0]
args = request[1:]
getattr(self, method + "Callback")(*args)
return task.cont
class Controller(object):
def __init__(self):
self.pipe, remote_pipe = Pipe()
self.app = QApplication([])
self.frontEnd = FrontEnd(self.pipe)
self.backEnd = Process(
target = BackEnd,
args = (self.frontEnd.getViewportID(), remote_pipe)
)
self.app.aboutToQuit.connect(self.onDestroy)
self.backEnd.start()
self.app.exec_()
def onDestroy(self, *args):
print "Destroy"
self.pipe.send(["close"])
self.backEnd.join(1)
if self.backEnd.is_alive():
self.backEnd.terminate()
if __name__ == "__main__":
c = Controller()
I tested this on Linux (Ubuntu) and Windows. You will obviously need PySide to run the code.
Just a note for anyone who tries to do serious stuff with this – make sure you don’t abuse the pipe. Pushing 60 messages/second in there doesn’t work – I tried ;D. Use Tasks.