How to setup panda and wxpython?

Hi,
I recently started using panda 1.8 and I’m excited about the better wxpython support (especially for Mac OSX). However I can’t figure out how to use it. I managed to set up wxpython and the direct.wxwidgets.WxPandaWindow class and the result looks like this:

But I can’t move the camera and it seams as if the panda app receives no events at all. I guess I set it up wrong, but i can’t find a offical way of doing it. Can you please help me?

This is my current code (some of it):

import wx
import wx.aui

from direct.showbase.ShowBase import ShowBase
from direct.wxwidgets.ViewPort import Viewport
import panda3d.core as core

core.loadPrcFileData('startup', 'window-type none')

class Frame(wx.Frame):
    def __init__(self, *args, **kwargs):
        wx.Frame.__init__(self, *args, **kwargs)

        self.auiManager = wx.aui.AuiManager(self)
        
        self.panda = Viewport.makeFront(self)
        self.outliner = scenegraph.Panel(self)
        
        outlinerInfo = wx.aui.AuiPaneInfo().Name("outliner")
        outlinerInfo.Left().CloseButton(False)
        outlinerInfo.Caption("Scenegraph").BestSize((300, 300))
        self.auiManager.AddPane(self.outliner, outlinerInfo)
        
        pandaInfo = wx.aui.AuiPaneInfo().Name("panda")
        pandaInfo.Center().CloseButton(False).MaximizeButton(True)
        self.auiManager.AddPane(self.panda, pandaInfo)
            
        self.auiManager.Update()
    
    def onQuit(self, evt):
        self.auiManager.UnInit()
        self.Close()

class App(ShowBase):
    def __init__(self):
        ShowBase.__init__(self, fStartDirect=False, windowType=None)        
        # setup application window        
        self.startWx()
        self.wxApp.Bind(wx.EVT_CLOSE, self.quit)
        self.frame = Frame(None, wx.ID_ANY, 'Editor', size=(1024, 768))
        self.frame.Show()
        
        # setup mouse/picking/selection system

        # THIS DOES NOT WORK, 
        # neither pressing the left/right mouse button
        # nor pressing t outputs "debug"
        def foo():
            print "debug"
        self.accept("mouse1", foo)
        self.accept("mouse3", foo)
        self.accept("t", foo)


        # load the scene (set up models and stuff)
        self.loadScene(self.args.map)

    def quit(self, event=None):
        self.onDestroy(event)
        try:
            base
        except NameError:
            sys.exit()
        base.userExit()

app = App()
app.run()

Thanks !

PS: I’m using Panda 1.8 and wxpython 2.9.3.1 on Windows 7.

1 Like

Hmm, if you put a print statement in ShowBase.py function __wxTimerCallback, does it show up? That would tell us if the timer that is supposed to run the Panda event loop is running properly at all.

I checked this and __wxTimerCallback gets called.

If you put “base.messenger.toggleVerbose()”, and try and send events to the Panda window, how do they appear (if at all) on the console?

I enabled base.messenger.toggleVerbose() and there are numerous log messages printed as the app is loading:

C:\open-emergency>python editor
Verbose mode true.  quiet list = ['collisionLoopFinished', 'event-loop-done', 'NewFrame', 'avatarMoving']
C:\Panda3D-1.8.0\direct\showbase\ShowBase.py:2740: wxPyDeprecationWarning: Using deprecated class.
  self.wxApp = wx.PySimpleApp(redirect = False)
Known pipe types:
  wglGraphicsPipe
(all display modules loaded.)
:display:windisplay(warning): SetActiveWindow() failed!
:display:windisplay(warning): SetForegroundWindow() failed!
:Messenger(debug): sent event: TaskManager-addTask sentArgs = [PythonTask resetPrevTransform], taskChain = None
:Messenger(debug): sent event: TaskManager-addTask sentArgs = [PythonTask dataLoop], taskChain = None
:Messenger(debug): sent event: TaskManager-addTask sentArgs = [PythonTask ivalLoop], taskChain = None
:Messenger(debug): sent event: TaskManager-addTask sentArgs = [PythonTask collisionLoop], taskChain = None
:Messenger(debug): sent event: TaskManager-addTask sentArgs = [PythonTask garbageCollectStates], taskChain = None
:Messenger(debug): sent event: TaskManager-addTask sentArgs = [PythonTask igLoop], taskChain = None
:Messenger(debug): sent event: TaskManager-addTask sentArgs = [PythonTask audioLoop], taskChain = None
:Messenger(debug): sent event: TaskManager-addTask sentArgs = [PythonTask eventManager], taskChain = None
:Messenger(debug): sent event: window-event sentArgs = [<libpanda.GraphicsWindow object at 0x064A5AD0>], taskChain = None
:Messenger(debug): sent event: window-event sentArgs = [<libpanda.GraphicsWindow object at 0x064A5AB8>], taskChain = None
:Messenger(debug): sent event: window-event sentArgs = [<libpanda.GraphicsWindow object at 0x064A5AD0>], taskChain = None
:Messenger(debug): sent event: open_window sentArgs = [<libpanda.GraphicsWindow object at 0x064A5A70>, True], taskChain = None
:Messenger(debug): sent event: open_main_window sentArgs = [], taskChain = None
:Messenger(debug): object: <editor.scenegraph.Tree; proxy of <Swig Object of type 'wxPyTreeCtrl *' at 0x6195820> > (('Tree', 5))
 accepting: tree-item-added
 method: <bound method Tree.onItemAdd of <editor.scenegraph.Tree; proxy of <Swig Object of type 'wxPyTreeCtrl *' at 0x6195820> >>
 extraArgs: []
 persistent: 1
:Messenger(debug): object: <editor.scenegraph.Tree; proxy of <Swig Object of type 'wxPyTreeCtrl *' at 0x6195820> > (('Tree', 5))
 accepting: tree-item-deleted
 method: <bound method Tree.onItemDelete of <editor.scenegraph.Tree; proxy of <Swig Object of type 'wxPyTreeCtrl *' at 0x6195820> >>
 extraArgs: []
 persistent: 1
:Messenger(debug): object: <editor.scenegraph.Tree; proxy of <Swig Object of type 'wxPyTreeCtrl *' at 0x6195820> > (('Tree', 5))
 accepting: tree-item-hidden
 method: <bound method Tree.onItemVisibilityChange of <editor.scenegraph.Tree; proxy of <Swig Object of type 'wxPyTreeCtrl *' at 0x6195820> >>
 extraArgs: []
 persistent: 1
:Messenger(debug): object: <editor.scenegraph.Tree; proxy of <Swig Object of type 'wxPyTreeCtrl *' at 0x6195820> > (('Tree', 5))
 accepting: tree-item-unhidden
 method: <bound method Tree.onItemVisibilityChange of <editor.scenegraph.Tree; proxy of <Swig Object of type 'wxPyTreeCtrl *' at 0x6195820> >>
 extraArgs: []
 persistent: 1
:Messenger(debug): object: <editor.scenegraph.Tree; proxy of <Swig Object of type 'wxPyTreeCtrl *' at 0x6195820> > (('Tree', 5))
 accepting: selection-empty
 method: <bound method Tree.UnselectAll of <editor.scenegraph.Tree; proxy of <Swig Object of type 'wxPyTreeCtrl *' at 0x6195820> >>
 extraArgs: []
 persistent: 1
:Messenger(debug): object: <editor.scenegraph.Tree; proxy of <Swig Object of type 'wxPyTreeCtrl *' at 0x6195820> > (('Tree', 5))
 accepting: selection-changed
 method: <bound method Tree.onSelectionChange of <editor.scenegraph.Tree; proxy of <Swig Object of type 'wxPyTreeCtrl *' at 0x6195820> >>
 extraArgs: []
 persistent: 1
:Messenger(debug): object: <editor.scenegraph.Tree; proxy of <Swig Object of type 'wxPyTreeCtrl *' at 0x6195820> > (('Tree', 5))
 accepting: map-loading-finished
 method: <function <lambda> at 0x064BF2F0>
 extraArgs: []
 persistent: 1
:Messenger(debug): object: <editor.App instance at 0x019043F0> (('App', 1))
 accepting: mouse1
 method: <function foo at 0x066B2A70>
 extraArgs: []
 persistent: 1
:Messenger(debug): object: <editor.App instance at 0x019043F0> (('App', 1))
 accepting: mouse3
 method: <function foo at 0x066B2A70>
 extraArgs: []
 persistent: 1
:Messenger(debug): object: <editor.App instance at 0x019043F0> (('App', 1))
 accepting: t
 method: <function foo at 0x066B2A70>
 extraArgs: []
 persistent: 1
:Messenger(debug): sent event: map-loading-finished sentArgs = [], taskChain = None
:Messenger(debug): sent event: window-event sentArgs = [<libpanda.GraphicsWindow object at 0x06BE48D8>], taskChain = None
:Messenger(debug): sent event: aspectRatioChanged sentArgs = [], taskChain = None
:Messenger(debug): sent event: TaskManager-addTask sentArgs = [PythonTask manager-update], taskChain = None
:Messenger(debug): sent event: window-event sentArgs = [<libpanda.GraphicsWindow object at 0x06BE48C0>], taskChain = None
:Messenger(debug): sent event: aspectRatioChanged sentArgs = [], taskChain = None
:Messenger(debug): sent event: window-event sentArgs = [<libpanda.GraphicsWindow object at 0x064A5EF0>], taskChain = None
:Messenger(debug): <direct.showbase.Loader.Loader instance at 0x06386D00> (('Loader', 0))
 now ignoring: 'async_loader_0'

However, pressing left/right mouse or pressing “t” doesn’t produce any log message. When I resize the window, “aspectRatioChanged” messages are sent.

Then I added messenger.send(“t”) after self.LoadScene and this works. foo() gets called and debug is printed to the console, as well as this log message:

:Messenger(debug): sent event: t sentArgs = [], taskChain = None

That is strange. Something in wxPython must be preventing the events from being sent to the Panda window. I don’t know enough about wxPython to say what, though.

Are you sure? I managed to get it working after a few changes in EmbeddedPandaWindow. I found this somewhere in the forum and it works like a charm.

class EmbeddedPandaWindow(wx.Window):
    """ 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']

        base.startWx()
        wx.Window.__init__(self, *args, **kw)

        wp = WindowProperties.getDefault()
        if platform.system() != 'Darwin':
            try:
                wp.setParentWindow(self.GetHandle())
            except OverflowError:
                # Sheesh, a negative value from GetHandle().  This can
                # only happen on 32-bit Windows.
                wp.setParentWindow(self.GetHandle() & 0xffffffff)

        #
        # CHANGE HERE
        # self.win = base.openWindow(props = wp, gsg = gsg, type = 'onscreen', unexposedDraw = False)
        base.openDefaultWindow(props=wp, gsg=None)
        self.win = base.win
        #
        
        self.Bind(wx.EVT_SIZE, self.onSize)

        # This doesn't actually do anything, since wx won't call
        # EVT_CLOSE on a child window, only on the toplevel window
        # that contains it.
        self.Bind(wx.EVT_CLOSE, self.__closeEvent)

    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())
        # CHANGE HERE
        self.win.requestProperties(wp)
        #
        event.Skip()

What do you think of this? Could it be that the issue is not caused by wxPython but by Panda? The rest of my code stayed the same, I just replaced

self.panda = Viewport.makeFront(self)

with

self.panda = WxPandaWindow(self)

Are you sure you needed to make the change to EmbeddedPandaWindow? I think it would work without that change, as long as you replaced your call to Viewport with a call to WxPandaWindow.

I think Viewport makes a lot of other assumptions that aren’t true in your case, but WxPandaWindow is closer to your environment.

David

When I try to run this as a test I get:

I have secondary display hooked up to my iMac, is that it? Not sure if I want to deactivate it and reboot the computer.

Did you try running it with pythonw instead of python, as it suggests? E.g. “pythonw demo.py”?

David

That gives:

Traceback (most recent call last):
  File "/Applications/Panda3D_Mare_Ceti/samples/WXPython/demo1.py", line 3, in <module>
    import wx
  File "/var/tmp/wxWidgets/wxWidgets-13~231/2.6/DSTROOT/System/Library/Frameworks/Python.framework/Versions/2.6/Extras/lib/python/wx-2.8-mac-unicode/wx/__init__.py", line 45, in <module>
  File "/var/tmp/wxWidgets/wxWidgets-13~231/2.6/DSTROOT/System/Library/Frameworks/Python.framework/Versions/2.6/Extras/lib/python/wx-2.8-mac-unicode/wx/_core.py", line 4, in <module>
ImportError: /System/Library/Frameworks/Python.framework/Versions/2.6/Extras/lib/python/wx-2.8-mac-unicode/wx/_core_.so: no appropriate 64-bit architecture (see "man python" for running in 32-bit mode)

python? ppython? pythonw? Oh man … :open_mouth:

I am having the same problem in windows.

I noticed that the ShowBase object’s mouseWatchNode is set to None.

Maybe there is some critical initialization which is not happening? Maybe there is a way to do it manually?

EDIT:
Is this necessary?
core.loadPrcFileData(‘startup’, ‘window-type none’)

I’ll bet the initialization of some of the input handlers doesn’t happen until the window exists.

Hmm… Further investigation:

The bottom of this page panda3d.org/manual/index.php/SPE

recommends that if the window-type is set to none, the window can be started later with this function:
base.openMainWindow(type = ‘onscreen’)

The docs for that function (ShowBase.openMainWindow) say:
Creates the initial, main window for the application, and sets up the mouse and render2d structures appropriately for it.

So, maybe input handlers are not being properly initialized because the window is not started?

EDIT 2:

If I omit the line
core.loadPrcFileData(‘startup’, ‘window-type none’)
then two windows show: the wxPython window, and the regular Panda3D window.

Input works in the Panda window, but not in the wxPython window.

class MainWindow(wx.Frame):
def init(self):
wx.Frame.init(self, None, -1, ‘Test’, size=(640, 480))
self.panda_panel = WxPandaWindow(parent=self)
self.Show(True)

EDIT 3:
Source code here: panda3d.cvs.sourceforge.net/view … xt%2Fplain

I need to read and understand what is going on there to track it down… looks like suppressing the window-open is the correct thing to do, since ther eis a call to base.openWindow inside the EmbeddedPandaWindow.

@Bradamante: it looks like there’s a mismatch between the architecture of your wx installation and the version of Python you’re running.

@doublereedkurt: on some platforms, wx wants control over the main loop in order for events to work properly. In 1.8.0 and above, this is done by Panda if you put “want-wx #t” in Config.prc (or use base.startWx()). Does that help?

@rdb: thanks, I didn’t know what startWx() did; it is being called so that probably isn’t the problem.

That is an interesting avenue though, I’ll try adding some wxPython widgets to the frame as well, and see if they are getting keyboard/mouse events.

I notice looking at the source code of WxPandaWindow that the windows class is much, much smaller than the OpenGL class. I wonder if some of that extra code is for passing through mouse/keyboard events when the panda window has focus.

Edit:
Ok, clearly the extra code has to do with passing through input events. The real question is, is that necessary for non-OpenGL windows? I’ll try Bind()'ing mouse and keyboard handlers on the window through wxPython’s normal mechanisms. Actually, it wouldn’t be too much code to have a pass-through binding. The only tricky part will be getting accurate screen coordinates for mouse clicks.

Also, I notice the aspect ratio doesn’t change when the window is re-sized.

You haven’t created any panda window at all, that’s what.
Stripped down version, beware :

import wx 
import wx.aui 

from direct.showbase.ShowBase import ShowBase 
from direct.wxwidgets.ViewPort import Viewport 
import panda3d.core as core 

core.loadPrcFileData('startup', 'window-type none') 

class Frame(wx.Frame): 
    def __init__(self, *args, **kwargs): 
        wx.Frame.__init__(self, *args, **kwargs) 

        self.auiManager = wx.aui.AuiManager(self) 
        
        self.panda = Viewport.makeFront(self)
 
        pandaInfo = wx.aui.AuiPaneInfo().Name("panda") 
        pandaInfo.Center().CloseButton(False).MaximizeButton(True) 
        self.auiManager.AddPane(self.panda, pandaInfo) 
            
        self.auiManager.Update() 
    
    def onQuit(self, evt): 
        self.auiManager.UnInit() 
        self.Close() 

class App(ShowBase): 
    def __init__(self): 
        ShowBase.__init__(self, fStartDirect=False, windowType=None)        
        # setup application window        
        self.startWx() 
        self.wxApp.Bind(wx.EVT_CLOSE, self.quit) 
        self.frame = Frame(None, wx.ID_ANY, 'Editor', size=(800, 600))

        # YNJH : create P3D window
        wp = core.WindowProperties()
        wp.setOrigin(20,20)
        wp.setSize(400,300)
        wp.setParentWindow(self.frame.panda.GetHandle())
        base.openMainWindow(type = 'onscreen', props=wp)

        self.frame.Show()
        
        # setup mouse/picking/selection system 

        def foo(): 
            print "debug" 
        self.accept("mouse1", foo) 
        self.accept("mouse3", foo) 
        self.accept("t", foo) 

    def quit(self, event=None): 
        self.onDestroy(event) 
        try: 
            base 
        except NameError: 
            sys.exit() 
        base.userExit() 

app = App() 
app.run()

@ynjh_jo

That makes a lot of sense!
Thank you so much :slight_smile:

Alright, so apparently WxPandaWindow / EmbeddedPandaWindow calls base.openWindow(). When you call openWindow() you get something without input handlers.

So, basically TLDR; if you want to enclose your game inside a wxWindows frame, it is totally doable, but you’ll have to do it manually and just use WxPandaWindow as a starting point.

@Bradamante

Try to use /usr/bin/pythonw2.5

Hi,
I have been using an embedded panda3d window into a wxpython panel without any problem, but recently I wanted to try the multi-thread Panda3D feature (Add “threading-model Cull/Draw” line to the config.prc file). Does anyone tried it? It does not work with my code, the app does not start properlly and gets stacked… I have tried to activate it with different code examples found in this thread and I have the same issue :frowning:
Any idea what is the problem? I attach my code if someone wants to try it…
Thanks!

import sys
import wx
from direct.showbase.ShowBase import ShowBase
from pandac.PandaModules import WindowProperties
from panda3d.core import LineSegs,Vec4

class MyP3DWindow(ShowBase):
    def __init__(self, panel):
        # Set PandaViewer to the panel
        self.wxpanel = panel
        assert self.wxpanel.GetHandle() != 0 
        # Open Panda3D
        wp = self.getwxPanelProp()  
        ShowBase.__init__(self, fStartDirect=False, windowType=None)
        base.openMainWindow(props = wp, gsg = None)
        self.wxpanel._win = base.win
        # Bind wxPanel
        self.wxpanel.Bind(wx.EVT_SIZE, self.OnResize)
        # Create World
        self.setCamera()
        self.drawGrid()

    def getwxPanelProp(self):
        wp = WindowProperties()
        wp.setOrigin(0,0)
        w, h = self.wxpanel.GetParent().GetClientSizeTuple()
        wp.setSize(w, h)
        wp.setParentWindow(self.wxpanel.GetHandle())
        return wp

    def OnResize(self, event): 
        wp = self.getwxPanelProp()  
        base.win.requestProperties(wp) 

    def drawGrid(self):
        self._GridMax   = 50
        self._GridInt   = 1
        base.setBackgroundColor(0.5, 0.5, 0.5)
        segs = LineSegs( ) 
        segs.setThickness( 1.0 ) 
        segs.setColor( Vec4(1,1,1,1) )
        for i in range(-self._GridMax,self._GridMax,self._GridInt):
            segs.moveTo(i,-self._GridMax,0)
            segs.drawTo(i,+self._GridMax,0)
            segs.moveTo(-self._GridMax,i,0)
            segs.drawTo(+self._GridMax,i,0)
        render.attachNewNode(segs.create()) 

    def setCamera(self):
        base.camera.setY(base.camera, 5) 


def updateWx(task):
  while MyWxWindow.Pending():
    MyWxWindow.Dispatch() 
  return task.cont

def OnClose(event):
  sys.exit()

# WxWidgets window system
MyWxWindow = wx.App(False)
frame = wx.Frame(None, wx.ID_ANY, "Hello World WxWidgets")
viewer = wx.Panel(frame, wx.ID_ANY)
frame.Show(True)
frame.Bind(wx.EVT_CLOSE, OnClose)

# Create Panda3D Viewer
MyPanda3D = MyP3DWindow(viewer)
taskMgr.add(updateWx, "updateWx")
run()

Unfortunately, the pipeline render system (“threading-model Cull/Draw”) does not work with wxWindows, or any parented windows, due to a race condition in the way the Windows events are handled.

David