Panda in PyQt

Hi all,

I did never get the examples here working, so I went experimenting and did get something thats working. This I wanted to share with you all. Also did some experimenting to get panda working with QGLWidget. Its working, but the Panda window stay’s open wathever I try. So I hope with these examples that we can have some desent QGLWidget without the Panda window.

without QGLWidget, eg with a normal QWidget

import platform
if platform.architecture()[0] != "32bit":
    raise Exception("Only 32bit architecture is supported")

from pandac.PandaModules import loadPrcFileData
#loadPrcFileData("", "window-type offscreen") # Set Panda to draw its main window in an offscreen buffer
loadPrcFileData("", "load-display pandagl")
loadPrcFileData("", "model-path c:/Panda3D-1.8.1/models")
loadPrcFileData("", "win-size 800 600")
loadPrcFileData("", "show-frame-rate-meter #t")
# PyQt imports
from PyQt4 import QtGui
from PyQt4 import QtCore
import os, sys

# Panda imports
from direct.showbase.DirectObject import DirectObject
from pandac.PandaModules import WindowProperties, GraphicsOutput,  Texture,  StringStream,  PNMImage
from direct.interval.LerpInterval import LerpHprInterval
from pandac.PandaModules import Point3
# Set up Panda environment
import direct.directbase.DirectStart
from struct import *

P3D_WIN_WIDTH = 800
P3D_WIN_HEIGHT = 600

def GetApplicationPath(file=None):
    import re, os
    # If file is None return current directory without trailing slash.
    if file is None:
        file = ""
    # Only when relative path.
    if not file.startswith("/") and not file.startswith("\\") and (
            not re.search(r"^[\w-]+:", file)):
        if hasattr(sys, "frozen"):
            path = os.path.dirname(sys.executable)
        elif "__file__" in globals():
            path = os.path.dirname(os.path.realpath(__file__))
        else:
            path = os.getcwd()
        path = path + os.sep + file
        path = re.sub(r"[/\\]+", re.escape(os.sep), path)
        path = re.sub(r"[/\\]+$", "", path)
        return path
    return str(file)

def ExceptHook(excType, excValue, traceObject):
    import traceback, os, time, codecs
    # This hook does the following: in case of exception write it to
    # the "error.log" file, display it to the console, shutdown CEF
    # and exit application immediately by ignoring "finally" (_exit()).
    errorMsg = "\n".join(traceback.format_exception(excType, excValue, traceObject))
    errorFile = GetApplicationPath("error.log")
    try:
        appEncoding = cefpython.g_applicationSettings["string_encoding"]
    except:
        appEncoding = "utf-8"
    if type(errorMsg) == bytes:
        errorMsg = errorMsg.decode(encoding=appEncoding, errors="replace")
    try:
        with codecs.open(errorFile, mode="a", encoding=appEncoding) as fp:
            fp.write("\n[%s] %s\n" % (time.strftime("%Y-%m-%d %H:%M:%S"), errorMsg))
    except:
        print("WARNING: failed writing to error file: %s" % (errorFile))
    # Convert error message to ascii before printing, otherwise
    # you may get error like this:
    # | UnicodeEncodeError: 'charmap' codec can't encode characters
    errorMsg = errorMsg.encode("ascii", errors="replace")
    errorMsg = errorMsg.decode("ascii", errors="replace")
    print("\n"+errorMsg+"\n")
    os._exit(1)

class MainWindow(QtGui.QMainWindow):
    mainFrame = None

    def __init__(self):
        super(MainWindow, self).__init__(None)
        panHandler = PandaHandler()
        self.mainFrame = MainFrame(panHandler.screenTexture)
        panHandler.bindToWindow(int(self.mainFrame.winId()))
        self.setCentralWidget(self.mainFrame)
        self.resize(800, 600)
        self.setWindowTitle('PyQT example')
        self.setFocusPolicy(QtCore.Qt.StrongFocus)

class MainFrame(QtGui.QWidget):
    def __init__(self, texture,  parent=None):
        super(MainFrame, self).__init__(parent)
       
        #self.setGeometry(50, 50, 800, 600)
        #self.setWindowTitle("PandaQt")
        #self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint) # Setup the window so its frameless
        self.pandaTexture = texture
       
        # Setup a timer in Qt that runs taskMgr.step() to simulate Panda's own main loop
        pandaTimer = QtCore.QTimer(self)
        self.connect(pandaTimer,  QtCore.SIGNAL("timeout()"), taskMgr.step)
        pandaTimer.start(0)
       
        # Setup another timer that redraws this widget in a specific FPS
        redrawTimer = QtCore.QTimer(self)
        self.connect(redrawTimer,  QtCore.SIGNAL("timeout()"), self, QtCore.SLOT("update()"))
        redrawTimer.start(1000/30)
       
        self.paintSurface = QtGui.QPainter()
        self.rotate = QtGui.QTransform()
        self.rotate.rotate(180)
       
        self.out_image = QtGui.QImage()
   
    def showEvent(self, event):
        self.desktopBg = QtGui.QPixmap.grabWindow(QtGui.QApplication.desktop ().winId(), self.geometry().x(),self.geometry().y(), self.rect().width(), self.rect().height())
   
    # Use the paint event to pull the contents of the panda texture to the widget
    def paintEvent(self,  event):
        if self.pandaTexture.mightHaveRamImage():
            self.pandaTexture.setFormat(Texture.FRgba32)
            #print "Should draw yes?"
            data = self.pandaTexture.getRamImage().getData()
            img = QtGui.QImage(data, self.pandaTexture.getXSize(), self.pandaTexture.getYSize(), QtGui.QImage.Format_ARGB32).mirrored()
            self.paintSurface.begin(self)
            self.paintSurface.drawPixmap(0, 0, self.desktopBg)
            self.paintSurface.drawImage(0, 0, img)
            self.paintSurface.end()
            pixmap = QtGui.QPixmap.fromImage(img)
            self.setMask(pixmap.mask())

class PandaHandler(DirectObject):
   
    def __init__(self):
        base.disableMouse()
        base.camera.setPos(0, -28, 6)
        self.testModel = loader.loadModel('panda.egg.pz')
        self.testModel.reparentTo(render)
        self.rotateInterval = LerpHprInterval(self.testModel, 3, Point3(360, 0, 0))
        self.rotateInterval.loop()
       
        self.screenTexture = Texture()
        self.screenTexture.setMinfilter(Texture.FTLinear)
        self.screenTexture.setFormat(Texture.FRgba32)
        print "Format is", self.screenTexture.getFormat()
        base.win.addRenderTexture(self.screenTexture, GraphicsOutput.RTMCopyRam)

    def bindToWindow(self, windowHandle):
        gsg = None
        wp = WindowProperties().getDefault()
        wp.setOrigin(0,0)
        wp.setSize(800, 600)
        if platform.system() != 'Darwin':
            try:
                wp.setParentWindow(windowHandle)
            except OverflowError:
                # Sheesh, a negative value from GetHandle().  This can
                # only happen on 32-bit Windows.
                wp.setParentWindow(windowHandle & 0xffffffff)
        #wp.setParentWindow(windowHandle)
        base.openWindow(props = wp, gsg = gsg, type = 'offscreen', unexposedDraw = False)
        self.wp = wp

if __name__ == '__main__':
    print("PyQt version: %s" % QtCore.PYQT_VERSION_STR)
    print("QtCore version: %s" % QtCore.qVersion())

    sys.excepthook = ExceptHook
    settings = {
        "log_file": GetApplicationPath("debug.log"),
        "release_dcheck_enabled": False # Enable only when debugging.
    }

    app = QtGui.QApplication(sys.argv)
    mainWindow = MainWindow()
    mainWindow.show()
    app.exec_()

    # Need to destroy QApplication(), otherwise Shutdown() fails.
    # Unset main window also just to be safe.
    del mainWindow
    del app

    sys.exit(0)

and with the QGLWidget

import platform
if platform.architecture()[0] != "32bit":
    raise Exception("Only 32bit architecture is supported")

from pandac.PandaModules import loadPrcFileData
#loadPrcFileData("", "window-type offscreen") # Set Panda to draw its main window in an offscreen buffer
loadPrcFileData("", "load-display pandagl")
loadPrcFileData("", "model-path c:/Panda3D-1.8.1/models")
loadPrcFileData("", "win-size 800 600")
loadPrcFileData("", "show-frame-rate-meter #t")
# PyQt imports
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4 import QtOpenGL
import OpenGL
OpenGL.ERROR_CHECKING = True
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL import error
oglError = error

import os, sys, platform

# Panda imports
from direct.showbase.DirectObject import DirectObject
from pandac.PandaModules import WindowProperties, FrameBufferProperties, CallbackGraphicsWindow, GraphicsOutput,  Texture,  StringStream,  PNMImage
from direct.interval.LerpInterval import LerpHprInterval
from pandac.PandaModules import Point3
# Set up Panda environment
import direct.directbase.DirectStart
from struct import *

P3D_WIN_WIDTH = 800
P3D_WIN_HEIGHT = 600

def GetApplicationPath(file=None):
    import re, os
    # If file is None return current directory without trailing slash.
    if file is None:
        file = ""
    # Only when relative path.
    if not file.startswith("/") and not file.startswith("\\") and (
            not re.search(r"^[\w-]+:", file)):
        if hasattr(sys, "frozen"):
            path = os.path.dirname(sys.executable)
        elif "__file__" in globals():
            path = os.path.dirname(os.path.realpath(__file__))
        else:
            path = os.getcwd()
        path = path + os.sep + file
        path = re.sub(r"[/\\]+", re.escape(os.sep), path)
        path = re.sub(r"[/\\]+$", "", path)
        return path
    return str(file)

def ExceptHook(excType, excValue, traceObject):
    import traceback, os, time, codecs
    # This hook does the following: in case of exception write it to
    # the "error.log" file, display it to the console, shutdown CEF
    # and exit application immediately by ignoring "finally" (_exit()).
    errorMsg = "\n".join(traceback.format_exception(excType, excValue, traceObject))
    errorFile = GetApplicationPath("error.log")
    appEncoding = "utf-8"
    if type(errorMsg) == bytes:
        errorMsg = errorMsg.decode(encoding=appEncoding, errors="replace")
    try:
        with codecs.open(errorFile, mode="a", encoding=appEncoding) as fp:
            fp.write("\n[%s] %s\n" % (time.strftime("%Y-%m-%d %H:%M:%S"), errorMsg))
    except:
        print("WARNING: failed writing to error file: %s" % (errorFile))
    # Convert error message to ascii before printing, otherwise
    # you may get error like this:
    # | UnicodeEncodeError: 'charmap' codec can't encode characters
    errorMsg = errorMsg.encode("ascii", errors="replace")
    errorMsg = errorMsg.decode("ascii", errors="replace")
    print("\n"+errorMsg+"\n")
    os._exit(1)

class MainWindow(QtGui.QMainWindow):
    mainFrame = None

    def __init__(self):
        super(MainWindow, self).__init__(None)
        self.mainFrame = EmbeddedPandaWindow()
        self.setCentralWidget(self.mainFrame)
        self.resize(800, 600)
        self.setWindowTitle('PyQT example')
        self.setFocusPolicy(QtCore.Qt.StrongFocus)

class EmbeddedPandaWindow(QtOpenGL.QGLWidget):
    """ This class implements a Panda3D window that is directly
    embedded within the frame.  It is fully supported on Windows,
    partially supported on Linux, and not at all on OSX. """

    def __init__(self, *args, **kw):
        gsg = None
        if 'gsg' in kw:
            gsg = kw['gsg']
            del kw['gsg']

        fbprops = kw.get('fbprops', None)
        if fbprops == None:
            fbprops = FrameBufferProperties.getDefault()

        QtOpenGL.QGLWidget.__init__(self, *args, **kw)
        
        base.disableMouse()
        base.camera.setPos(0, -28, 6)
        self.testModel = loader.loadModel('panda.egg.pz')
        self.testModel.reparentTo(render)
        self.rotateInterval = LerpHprInterval(self.testModel, 3, Point3(360, 0, 0))
        self.rotateInterval.loop()

        self.callbackWindowDict = {
            'Events' : self.__eventsCallback,
            'Properties' : self.__propertiesCallback,
            'Render' : self.__renderCallback,
            }

        # Make sure we have an OpenGL GraphicsPipe.
        if not base.pipe:
            base.makeDefaultPipe()
        self.pipe = base.pipe
        if self.pipe.getInterfaceName() != 'OpenGL':
            base.makeAllPipes()
            for self.pipe in base.pipeList:
                if self.pipe.getInterfaceName() == 'OpenGL':
                    break

        if self.pipe.getInterfaceName() != 'OpenGL':
            raise StandardError, "Couldn't get an OpenGL pipe."

        self.win = base.openWindow(callbackWindowDict = self.callbackWindowDict, pipe = self.pipe, gsg = gsg, type = 'none', unexposedDraw = True)

        # Setup a timer in Qt that runs taskMgr.step() to simulate Panda's own main loop
        pandaTimer = QtCore.QTimer(self)
        self.connect(pandaTimer,  QtCore.SIGNAL("timeout()"), taskMgr.step)
        pandaTimer.start(0)

    def closeEvent(self, event):
        self.cleanup()
        event.Skip()

    def cleanup(self):
        """ Parent windows should call cleanup() to clean up the
        wxPandaWindow explicitly (since we can't catch EVT_CLOSE
        directly). """
        if self.win:
            base.closeWindow(self.win)
            self.win = None

    def onSize(self, event):
        wp = WindowProperties()
        wp.setOrigin(0, 0)
        wp.setSize(*self.GetClientSize())
        self.win.requestProperties(wp)
        event.Skip()

    def __eventsCallback(self, data):
        data.upcall()

    def __propertiesCallback(self, data):
        data.upcall()

    def __renderCallback(self, data):
        cbType = data.getCallbackType()
        if cbType == CallbackGraphicsWindow.RCTBeginFrame:
            if not self.isVisible():
                data.setRenderFlag(False)
                return
            self.updateGL()

            # Don't upcall() in this case.
            return
        
        elif cbType == CallbackGraphicsWindow.RCTEndFlip:
            # Now that we've swapped, ask for a refresh, so we'll
            # get another paint message if the window is still
            # visible onscreen.
            self.updateGL()

        data.upcall()

if __name__ == '__main__':
    print("PyQt version: %s" % QtCore.PYQT_VERSION_STR)
    print("QtCore version: %s" % QtCore.qVersion())

    sys.excepthook = ExceptHook
    settings = {
        "log_file": GetApplicationPath("debug.log"),
        "release_dcheck_enabled": False # Enable only when debugging.
    }

    app = QtGui.QApplication(sys.argv)
    mainWindow = MainWindow()
    mainWindow.show()
    app.exec_()

    # Need to destroy QApplication(), otherwise Shutdown() fails.
    # Unset main window also just to be safe.
    del mainWindow
    del app

    sys.exit(0)

I hope that some of you can help to make these examples working, so that beginners can benefit from our work…

Happy Panda coding!!!