Panda in PyQt

Here’s early code on getting Panda integrated with PyQt as a widget. Its really slow since converting the frame buffer into images PyQt can use is really slow, theres probably a better way though.

To run the demo you’ll need PyQt4

from pandac.PandaModules import loadPrcFileData
#loadPrcFileData("", "window-type offscreen") # Set Panda to draw its main window in an offscreen buffer
# PyQt imports
from PyQt4 import QtGui,  QtCore
import sys
# Panda imports
from direct.showbase.DirectObject import DirectObject
from pandac.PandaModules import GraphicsOutput,  Texture,  StringStream,  PNMImage
from direct.interval.LerpInterval import LerpHprInterval
from pandac.PandaModules import Point3
# Set up Panda environment
import direct.directbase.DirectStart

class QtPandaWidget(QtGui.QWidget):
    """
    This takes a texture from Panda and draws it as a QWidget
    """
    
    def __init__(self, texture,  parent=None):
        QtGui.QWidget.__init__(self,  parent)
        
        self.setGeometry(50, 50, 800, 600)
        self.setWindowTitle("PandaQt")
        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/60)
        
        # Setup a QLabel to paint the pixmap on
        self.paintSurface = QtGui.QLabel(self)
        self.paintSurface.setGeometry(0, 0, 800, 600)
        self.paintSurface.show()
        
        self.paintPixmap = QtGui.QPixmap(800, 600)
    
    # Use the paint event to pull the contents of the panda texture to the widget
    def paintEvent(self,  event):
        
        screenData = StringStream() # Used to pass the data as a string
        screenImage = PNMImage() # Converts the texture data into a format usable with Qt
        
        if self.pandaTexture.hasRamImage():
            print "Should draw yes?"
            self.pandaTexture.store(screenImage)
            screenImage.write(screenData, "test.ppm")
            self.paintPixmap.loadFromData(screenData.getData())
            self.paintSurface.setPixmap(self.paintPixmap)

class PandaHandler(DirectObject):
    
    def __init__(self):
        base.disableMouse()
        base.cam.setPos(0, -28, 6)
        self.testModel = loader.loadModel('panda')
        self.testModel.reparentTo(render)
        print self.testModel.getPos()
        self.rotateInterval = LerpHprInterval(self.testModel, 3, Point3(360, 0, 0))
        self.rotateInterval.loop()
        
        self.screenTexture = Texture()
        self.screenTexture.setMinfilter(Texture.FTLinear)
        base.win.addRenderTexture(self.screenTexture, GraphicsOutput.RTMCopyRam)

if __name__ == "__main__":    
    #lol teh hobo
    panHandler = PandaHandler() 
    
    app = QtGui.QApplication(sys.argv)
    pandaWidget = QtPandaWidget(panHandler.screenTexture)
    pandaWidget.show()
    
    sys.exit(app.exec_())
1 Like

Ooh, made some minor changes to the code and got masked windows in. Probably can’t go by this route to get decent speed though. You can see the redraws on it, nasty.

Code changes, copy/paste friendly

...

class QtPandaWidget(QtGui.QWidget):
    """
    This takes a texture from Panda and draws it as a QWidget
    """
    
    def __init__(self, texture,  parent=None):
        QtGui.QWidget.__init__(self,  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

...

    # Use the paint event to pull the contents of the panda texture to the widget
    def paintEvent(self,  event):
        
        screenData = StringStream() # Used to pass the data as a string
        screenImage = PNMImage() # Converts the texture data into a format usable with Qt
        
        if self.pandaTexture.mightHaveRamImage(): #ambigious!
            print "Should draw yes?"
            self.pandaTexture.store(screenImage)
            print "Has alpha?",screenImage.hasAlpha()
            screenImage.write(screenData, "test.png")
            self.paintPixmap.loadFromData(screenData.getData())
            self.paintSurface.setPixmap(self.paintPixmap)
            self.setMask(self.paintPixmap.mask())
...

Picture!

Current state:

Drawing into PyQt windows is faster now without image conversion getting in the way. If you wished to make a PyQt app with a native panda component (for GUI’s maybe?) this is a pretty good way to do it. Still not satisfied though as the alpha masking for the window still has to go through conversion and is therefore slow.

Still not satisfied though

from pandac.PandaModules import loadPrcFileData
#loadPrcFileData("", "window-type offscreen") # Set Panda to draw its main window in an offscreen buffer
# PyQt imports
from PyQt4 import QtGui,  QtCore
import sys
# Panda imports
from direct.showbase.DirectObject import DirectObject
from pandac.PandaModules import 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 *

class QtPandaWidget(QtGui.QWidget):
    """
    This takes a texture from Panda and draws it as a QWidget
    """
    
    def __init__(self, texture,  parent=None):
        QtGui.QWidget.__init__(self,  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/60)
        
        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.cam.setPos(0, -28, 6)
        self.testModel = loader.loadModel('panda')
        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)

if __name__ == "__main__":    
    #lol teh hobo
    panHandler = PandaHandler() 
    
    app = QtGui.QApplication(sys.argv)
    pandaWidget = QtPandaWidget(panHandler.screenTexture)
    pandaWidget.show()
    
    sys.exit(app.exec_())

Wow. This is so interesting! Good job! :slight_smile:

digging out this thread. i just wondered if it would be possible to let a shader do the masking somehow , let it be a buffer or something. i dunno a thing about qt but it seems like a stupid conversion shaders would be fast enough for. just in case it’s actually possible.

I don’t know, never really tried shaders before. It would also seem to me like a conversion shader would be much more cross compatible. One of the things that I’ve found about the code below is the differences between how the frames are stored in the graphics card makes the code very specific to the kind of card you’re running.

just thought about using panda for a window-sitter.
given qt4 there might be a nice way to speed alpha-masking up. thought i dunno if its possible but it looks promising to somone who has no idea about qt (->me)
http://doc.trolltech.com/4.3/qtopengl.html
anyone experienced enough with the topic can tell me if its possible to render a transparent window on top of the screen? (without going through all-to-slow-software-conversions , and assuming a linux system , if you like with working hw-acc desktop)
thx for the brain-capacity you take to anwer the question :slight_smile:

no, i dont think so.

Qt has an OpenGL component that is free to use for open source projects I think. Doing a partially transparent window might be much more faster there and less hardware/OS specific. I haven’t really looked into the OpenGL part of Qt though and I’m not even sure if PyQT has access to that.

there is a PyQt-GL binding.thought i havent found any usefull documentation on that one.
any other techniques to draw a partly transparent window ontop of the screen? video-overlays or something like that?

I don’t really know, I’ve asked before on the PyQT mailing list if there’s a faster way to do semi-transparent windows and didn’t really get a reply. I didn’t ask about PyQT-GL though.

Hey, I’m really interested in this - Qt is my other recent addiction, along with Panda, so it’s my two current favorite things :slight_smile:

When panda creates its main window, is there a way to parent it to another window? I don’t even know what I’m talking about completely, I just gathered this from reading other peoples’ Ogre+QT code. that might be more efficient than rendering to a texture and then converting it to a qimage.

Wonder if you could also use the QGLPixelBuffer for this?

I don’t know if this wasn’t available before, but I managed to embed a Panda window in QT simply by passing it the window handle of the container.

I wrote this small script with PyQT to test if the keyboard focus was working correctly as with wxPython on Ubuntu there are problems:

# -*- coding: utf-8-*-

from pandac.PandaModules import loadPrcFileData
loadPrcFileData("", "window-type none")

import direct.directbase.DirectStart
from direct.showbase.DirectObject import DirectObject
from pandac.PandaModules import WindowProperties

from PyQt4.QtCore import *
from PyQt4.QtGui import *

import sys

P3D_WIN_WIDTH = 400
P3D_WIN_HEIGHT = 240

class QTTest(QDialog):
    def __init__(self, pandaCallback, parent=None):
        super(QDialog, self).__init__(parent)
        self.setWindowTitle("Test")
        self.setGeometry(0,0,400,300)
        
        self.pandaContainer = QWidget(self)
        self.pandaContainer.setGeometry(0,0,P3D_WIN_WIDTH,P3D_WIN_HEIGHT)

        self.lineedit = QLineEdit("Write something...does it work?")
        
        layout = QVBoxLayout()
        layout.addWidget(self.pandaContainer)
        layout.addWidget(self.lineedit)
        
        self.setLayout(layout)
        
        # this basically creates an idle task
        timer =  QTimer(self)
        self.connect( timer, SIGNAL("timeout()"), pandaCallback )
        timer.start(0)

    
class World(DirectObject):   
    def __init__(self):
        self.accept("a", self.pressedA)
        self.accept("escape", sys.exit)
    
    def pressedA(self):
        print "a pressed, keyboard focus ok"
        
    def step(self):
        taskMgr.step()
    
    def bindToWindow(self, windowHandle):
        wp = WindowProperties().getDefault()
        wp.setOrigin(0,0)
        wp.setSize(P3D_WIN_WIDTH, P3D_WIN_HEIGHT)
        wp.setParentWindow(windowHandle)
        base.openDefaultWindow(props=wp)
        self.wp = wp
        
    
if __name__ == '__main__':
    world = World()

    app = QApplication(sys.argv)
    form = QTTest(world.step)
    world.bindToWindow(int(form.winId()))
    
    form.show()
    app.exec_()

I tried it in Ubuntu and it simply works! Thanks! I also tried it in Windows, but without much success. The controls in Panda3D scene are working, but as soon as you move focus to edit control, it cannot be changed back to Panda3D panel (or I didn’t found a way). Any solutions for this?

On Ubuntu I just click inside the panda window in order to regain focus, on Windows I can’t test, maybe try to set the focus policy on the pandaContainer widget explicitely:

self.pandaContainer.setFocusPolicy(Qt.StrongFocus)

It’s just a wild guess I don’t know if it’ll works.

I’ll try that tonight (my machine at work is Ubuntu :slight_smile: ), thanks. BTW, for this embedding to work in Windows, bindToWindow must be called with casting:

world.bindToWindow(int(form.winId())) 

I added your fix to the code above, thanks.

Unfortunately, it didn’t worked. Regaining focus is (for now) a no-no. On Ubuntu everything is working fine from the beginning. Maybe I should post the question at PyQt forum (maybe it isn’t Panda-related). Anyway, thanks for trying. BTW, I’m using your code for a little project of mine (https://discourse.panda3d.org/viewtopic.php?t=5521)

By adding (modified version of drwr’s answer for wxWidgets):

class QTPandaWidget(QWidget):
	def __init__(self, parent=None):
		super(QWidget, self).__init__(parent)
		
	def resizeEvent(self, evt):
		wp = WindowProperties()
		wp.setSize(self.width(), self.height())
		wp.setOrigin(self.x(),self.y())
		base.win.requestProperties(wp)

and replacing

self.pandaContainer = QWidget(self)

with

self.pandaContainer = QTPandaWidget(self)

you can make panda window resizeable in PyQt.

Edit: Solved positioning issues (I hope…)
Edit2: It doesn’t work with QHBoxLayout, for example… :frowning:

Now it works with QHBoxLayout…

class QTPandaWidget(QWidget):
	def __init__(self, parent=None):
		super(QWidget, self).__init__(parent)
		self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding))
		
	def resizeEvent(self, evt):
		wp = WindowProperties()
		wp.setSize(self.width(), self.height())
		wp.setOrigin(self.x(),self.y())
		base.win.requestProperties(wp)
	
	def minimumSizeHint(self):
		return QSize(400,300)