Panda3D and wxPython?

Has anyone tried rendering Panda3D in a wxPython window?

Currently, Panda will not render into a subordinate window: it insists on creating its own toplevel windows for rendering into. So you can’t use Panda to render directly into a wxPython window. However, this is probably not a difficult feature to add; we just haven’t gotten around to it.

I have, however, used wxPython with a Panda application before, just not in the same window.

David

How did you manage that?

What do you mean? Just “import wx” in your Panda application, and you’re off to the races.

David

Oh I see - I thought there was some secret incantation you had to do! :laughing:

I thought it was more involved than that.

I can create both Panda3D and wxPython windows, but Panda3D won’t do anything. The tutorials say to call run(), but if I do that, then the wxPython app never gets control and doesn’t display the window.

If I don’t call run(), both windows are displayed and the wxPython window works, but Panda3D doesn’t do anything.

What’s the secret incantation? :wink:

Oh, right–you do have to resolve the fighting over the main loop.

Panda’s run() function, and wx’s app.MainLoop() method, both are designed to handle all events and never return–they are both supposed to serve as the one main loop of the application. Obviously, you can’t have two main loops.

Fortunately, both Panda and wx also supply a function that you can just call occasionally, instead of a function that never returns. In Panda’s case, it’s taskMgr.step(); in wx’s case, it’s app.Dispatch(). So you can choose whether you want to make wx handle the main loop, and call taskMgr.step() from time to time; or whether you want to make Panda handle the main loop, and call app.Dispatch() from time to time.

I think it works better to make Panda handle the main loop. So that just means you need to start a task to call app.Dispatch() every frame, if needed. Instead of calling app.MainLoop(), do something like the following:

app = wx.App(0)

def handleWxEvents(task):
    while app.Pending():
        app.Dispatch()
    return Task.cont

taskMgr.add(handleWxEvents, 'handleWxEvents')
run()

David

Ok I got it working with Panda3D controlling the main loop - I’ll see if I can write a version with wxPython controlling the loop. See if there’s any difference in performance. I added a sleep(0.5) to make Panda3D use less CPU.

sleep(0.5) might be a bit much; that will significantly impact your frame rate. You can significantly reduce CPU with as little as sleep(0.001).

Using wx as the main loop will certainly use less CPU, but you may not get very good response out of the 3-D window. The reason is that wx is not designed to support a real-time 3-D application, and its callback events are generally generated only at infrequent intervals (based on when the user is interacting with the window). This is appropriate for a 2-D application that is completely response-driven, but not very useful for a 3-D application that continues to be active even when you are not interacting with it. Depending on your application, that may or may not be a problem for you. :slight_smile:

David

I know this is an old thread, but I thought I would add something to it for future searches.

No please keep in mind this is a very, very simple piece of code that I just cut and pasted from the Panda manual and the wxpython.org site so it was just proof-of-concept and doesn’t really do anything.

I never liked the Tk GUI. It was ok years ago, but it’s kind of dated now. What I’ve decided to do in my app is use wxPython (for Windows) for some of the 2d game elements kind of like Photoshop does with the floating tool bars. I’m not sure if this is a good UI thing to do or not but it definately is a good shortcut for me and my limited programming skills :slight_smile:


import direct.directbase.DirectStart
from direct.task import Task
from wxPython.wx import *

class MyApp(wxApp):
    def OnInit(self):
        frame = wxFrame(NULL, -1, "Hello from wxPython")
        frame.Show(true)
        self.SetTopWindow(frame)
        return true

app = MyApp(0)

def handleWxEvents(task):
    while app.Pending():
        app.Dispatch()
    return Task.cont

taskMgr.add(handleWxEvents, 'handleWxEvents') 
 
run()

Maybe you can build on your code from here.

Steve

Here’s something that actually does a little something… well, just for show.


import direct.directbase.DirectStart
from direct.task import Task
import wx

class TestFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1, "Real World Test")
        panel = wx.Panel(self)

        # First create the controls
        topLbl = wx.StaticText(panel, -1, "Account Information")   
        topLbl.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD))

        nameLbl = wx.StaticText(panel, -1, "Name:")
        name = wx.TextCtrl(panel, -1, "");

        addrLbl = wx.StaticText(panel, -1, "Address:")
        addr1 = wx.TextCtrl(panel, -1, "");
        addr2 = wx.TextCtrl(panel, -1, "");

        cstLbl = wx.StaticText(panel, -1, "City, State, Zip:")
        city  = wx.TextCtrl(panel, -1, "", size=(150,-1));
        state = wx.TextCtrl(panel, -1, "", size=(50,-1));
        zipcd = wx.TextCtrl(panel, -1, "", size=(70,-1));
        
        phoneLbl = wx.StaticText(panel, -1, "Phone:")
        phone = wx.TextCtrl(panel, -1, "");

        emailLbl = wx.StaticText(panel, -1, "Email:")
        email = wx.TextCtrl(panel, -1, "");

        saveBtn = wx.Button(panel, -1, "Save")
        cancelBtn = wx.Button(panel, -1, "Cancel")

        # Now do the layout.

        # mainSizer is the top-level one that manages everything
        mainSizer = wx.BoxSizer(wx.VERTICAL)                    
        mainSizer.Add(topLbl, 0, wx.ALL, 5)                     
        mainSizer.Add(wx.StaticLine(panel), 0,                  
                wx.EXPAND|wx.TOP|wx.BOTTOM, 5)                  

        # addrSizer is a grid that holds all of the address info
        addrSizer = wx.FlexGridSizer(cols=2, hgap=5, vgap=5)    
        addrSizer.AddGrowableCol(1)                             
        addrSizer.Add(nameLbl, 0,                               
                wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)        
        addrSizer.Add(name, 0, wx.EXPAND)                       
        addrSizer.Add(addrLbl, 0,                               
                wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)        
        addrSizer.Add(addr1, 0, wx.EXPAND)                      
        addrSizer.Add((10,10)) # some empty space               
        addrSizer.Add(addr2, 0, wx.EXPAND)                      

        addrSizer.Add(cstLbl, 0, 
                wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
        
        # the city, state, zip fields are in a sub-sizer
        cstSizer = wx.BoxSizer(wx.HORIZONTAL)                   
        cstSizer.Add(city, 1)                                   
        cstSizer.Add(state, 0, wx.LEFT|wx.RIGHT, 5)             
        cstSizer.Add(zipcd)                                      
        addrSizer.Add(cstSizer, 0, wx.EXPAND)                   
        
        addrSizer.Add(phoneLbl, 0,                                  
                wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)        
        addrSizer.Add(phone, 0, wx.EXPAND)                      
        addrSizer.Add(emailLbl, 0,                              
                wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)        
        addrSizer.Add(email, 0, wx.EXPAND)                      

        # now add the addrSizer to the mainSizer
        mainSizer.Add(addrSizer, 0, wx.EXPAND|wx.ALL, 10)       

        # The buttons sizer will put them in a row with resizeable
        # gaps between and on either side of the buttons
        btnSizer = wx.BoxSizer(wx.HORIZONTAL)                   
        btnSizer.Add((20,20), 1)                                
        btnSizer.Add(saveBtn)                                   
        btnSizer.Add((20,20), 1)                                
        btnSizer.Add(cancelBtn)                                 
        btnSizer.Add((20,20), 1)                                

        mainSizer.Add(btnSizer, 0, wx.EXPAND|wx.BOTTOM, 10)

        panel.SetSizer(mainSizer)

        # Fit the frame to the needs of the sizer.  The frame will
        # automatically resize the panel as needed.  Also prevent the
        # frame from getting smaller than this size.
        mainSizer.Fit(self)                                     
        mainSizer.SetSizeHints(self)


app = wx.App()
TestFrame().Show()

def handleWxEvents(task):
    while app.Pending():
        app.Dispatch()
    return Task.cont

taskMgr.add(handleWxEvents, 'handleWxEvents') 
 
run()

Of course, make sure you have wxPython installed.

Steve

I get an ‘ImportError’. It says that there is no module wx, but I can run wx applications without panda. Does this have something to do with running ppython?

By default wxPython and related stuff is installed under the Python version outside of Panda3D if you have one. I just copied it from the “site-python” directory from that version of Python to put under the Python version that comes with Panda3D. Does that make sense?

I sort of know what you are talking about…just the way panda is setup is a bit strange. I thought ppython was just an executable that called on python?? So any library we want to use we should copy to the panda directory?

Well, that’s just how I do it.

I install the module I need into my Python distribution outside of Panda3D and then copy them over to:

C:\Panda3D-1.2.3\bin\lib\site-packages

Panda3D Manual VII.C.1 SPE. That just talks about SPE, but it should apply equally to anything that installs in site-packages