fully working wxpython+panda3d example

Hi all
After hours of frustrated digging in the forum on how to combine wx with panda, and after I finaly did the job, i post the full solution here.
The terrain is real terrain of the small crater (=machtesh) in Israel, one of the most beautifuls…
Copy attached files to your “/tmp” directory
I’m working on ubuntu 14.04, python 2.7.6, wxpython 2.8.12.1, panda 1.9.1

import wx
from panda3d.core import loadPrcFileData
from direct.showbase.ShowBase import ShowBase   # import the bits of panda
from direct.wxwidgets.WxPandaWindow import WxPandaWindow
from panda3d.core import *
from direct.wxwidgets.WxPandaWindow import WxPandaWindow
from direct.task import Task

class PandaPanel(wx.Panel):

    def __init__(self, parent, id, style):
        wx.Panel.__init__(self, parent, id)
        self.SetBackgroundColour("BLUE")

    def OnKeyDown(self, event):
        keycode = event.GetKeyCode()
        print "PandaPanel OnKeyDown " + str(keycode)

    def OnKeyUp(self, event):
        keycode = event.GetKeyCode()
        print "PandaPanel OnKeyUp " + str(keycode)


class pandaApp(ShowBase):                          # our 'class'

    def __init__(self):
        ShowBase.__init__(self)                        # initialise
        base.startWx()

        # add ambient light
        ambient = Vec4(0.34, 0.3, 0.3, 1)
        alight = AmbientLight('alight')
        alight.setColor(ambient)
        alnp = render.attachNewNode(alight)
        
        dlight = DirectionalLight('dlight')
        dlight.setColor(VBase4(0.8, 0.8, 0.5, 1))
        dlnp = render.attachNewNode(dlight)
        dlnp.setHpr(0, -60, 0)
        
        

        terrain = GeoMipTerrain("genTerrain")
        terrain.setHeightfield("/tmp/small_crater.hm.jpg")
        terrain.setBlockSize(64)
        terrain.setBruteforce(1)         
        self.ter = terrain
        # Store the root NodePath for convenience
        self.root = terrain.getRoot()


        # self.root = base.loader.loadModel('/tmp/crater.4.bam')
        self.root.reparentTo(base.render)
        self.root.setScale(4)
        self.hm_size=512
        self.root.setPos(-1*self.hm_size*self.root.getSx()/2,
                         -1*self.hm_size*self.root.getSy()/2,0)
        self.root.setHpr(0, 0, 0)
        self.root.setSz(200)
        terrain.generate()
        ######  Load textures and alpha maps
        tex0 = loader.loadTexture('/tmp/small_crater.txt.2.jpg')
        render.setLight(alnp)
        render.setLight(dlnp)     
        
        self.blendtex(tex0, "finalstage", 200)

        self.taskMgr.add(self.spinCameraTask, "SpinCameraTask")

        base.disableMouse()
        self.camera.setPos(0, 0, 3)

    def blendtex(self, texture, stage, SORT):
        ts = TextureStage(stage)
        ts.setSort(SORT)
        # ts.setCombineRgb(TextureStage.CMModulate, TextureStage.CSPrevious, TextureStage.COSrcColor,
        #                                               TextureStage.CSTexture, TextureStage.COSrcColor)
        ts.setRgbScale(2)
        self.root.setTexture(ts, texture)
        ts.setSavedResult(True)

    # Define a procedure to move the camera.
    def spinCameraTask(self, task):
        dt = globalClock.getDt()

        if wx.GetKeyState(wx.WXK_LEFT):
            self.camera.setH(self.camera.getH() + 20 * dt)
        elif wx.GetKeyState(wx.WXK_RIGHT):
            self.camera.setH(self.camera.getH() - 20 * dt)
        if wx.GetKeyState(wx.WXK_UP):
            self.camera.setY(self.camera, +20 * dt)
        elif wx.GetKeyState(wx.WXK_DOWN):
            self.camera.setY(self.camera, -20 * dt)

        (hmx,hmy) = (self.hm_size/2+self.camera.getX()/self.root.getSx(),
                     self.hm_size/2+(self.camera.getY()+base.camLens.getNear())/self.root.getSy())
        self.camera.setZ(self.ter.getElevation(hmx, hmy)*self.root.getSz()+3)

        return Task.cont



class MainWindow(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent, -1, 'Test', size=(800, 600))

        pandaPanel = PandaPanel(self, -1, style=wx.SUNKEN_BORDER)
        panel2 = wx.Panel(self, -1, style=wx.SUNKEN_BORDER)

        panel2.SetBackgroundColour("RED")

        panel2.Bind(wx.EVT_LEFT_DOWN, self.OnMouseEvent2)
        panel2.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown2)


        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(pandaPanel, 2, wx.EXPAND)
        sizer.Add(panel2, 1, wx.EXPAND)
        self.SetSizer(sizer)

        panelSizer = wx.BoxSizer(wx.VERTICAL)

        self.window1 = WxPandaWindow(parent=pandaPanel)
        panelSizer.Add(self.window1, 1, wx.EXPAND)
        pandaPanel.SetSizer(panelSizer)

        self.window1.Bind(wx.EVT_LEFT_DOWN, self.OnMouseEvent)
        self.window1.Bind(wx.EVT_LEFT_UP, self.OnMouseEvent)
        self.window1.Bind(wx.EVT_KEY_DOWN, pandaPanel.OnKeyDown)
        self.window1.Bind(wx.EVT_KEY_UP, pandaPanel.OnKeyUp)

        self.Layout()
        self.Show(True)
        self.panel2 = panel2

    def OnMouseEvent(self, event):
        print "OnMouseEvent " + str(event.Id)
        # print str(self.FindFocus())
        self.window1.SetFocus()

    def OnKeyDown(self, event):
        keycode = event.GetKeyCode()
        print "main OnKeyDown " + str(keycode)

    def OnKeyUp(self, event):
        keycode = event.GetKeyCode()
        print "main OnKeyUp " + str(keycode)

    def OnMouseEvent2(self, event):
        print "OnMouseEvent2 " + str(event.Id)
        self.panel2.SetFocus()

    def OnKeyDown2(self, event):
        keycode = event.GetKeyCode()
        print "OnKeyDown2 " + str(keycode)
        self.panel2.SetFocus()

# loadPrcFileData('startup', 'window-type none')
#  window-type none is for where you don't need window, for
#    example when you generate files. There is no camera in 
#    such situation. Here we need camera, so window is
#    offscreen, not none
loadPrcFileData('startup', 'window-type offscreen')
app = pandaApp()                                   # our 'object'
mw = MainWindow(None)
run()




Thank you, Elkav

very useful

PhilippeC

Hello Elkav. Thank you for the example. I tested your code: I am able to move camera aoround after I focus on the red panel. However, “OnMouseEvent”, “OnKeyDown” and “OnKeyUp” events of the MainWindow class never get called. Whenever the focus is on the Panda window, it recieves no events.

Does this code behave differently on your system? I am running Windows 7 64-bit, Python 2.7.5 64 bit. My Panda3D version is 1.9 and wxPython version is ‘3.0.2.0 msw (classic)’.

I also searched the forum and found this topic: How to setup panda and wxpython?
ynjh_jo offers a solution, using Viewport class. It works! Is this how it is supposed to be done? unfortunately, there is nothing in the documentation, and I don’t know neither wx nor Panda3D to figure out.

I’ve revisited ynjh_jo’s code and it turned out that we don’t have to use the Viewport class (it was taken from the official Panda editor, it draws grid and sets perspective). I’ve also removed lines I don’t understand (such as regarding wx.aui.Manager) and got the minimal working example that shows both Panda3D window and wxPython widget:

import wx

from direct.showbase.ShowBase import ShowBase
import panda3d.core as core

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

        # add menu
        menubar = wx.MenuBar()
        fileMenu = wx.Menu()
        fitem = fileMenu.Append(wx.ID_EXIT, 'Quit', 'Quit application')
        self.Bind(wx.EVT_MENU, self.onQuit, fitem)
        menubar.Append(fileMenu, '&File')
        self.SetMenuBar(menubar)
   
    def onQuit(self, evt):
        self.Close()

class App(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        
        # setup application window       
        self.startWx()
        self.wxApp.Bind(wx.EVT_CLOSE, self.quit)
        self.frame = Frame(None, wx.ID_ANY, 'Editor')
        self.frame.Show()
        self.frame.Layout()
        
        # YNJH : create P3D window
        wp = core.WindowProperties()
        wp.setOrigin(20,20)
        wp.setSize(400,300)
        wp.setParentWindow(self.frame.GetHandle())
        base.openMainWindow(type = 'onscreen', props=wp, size=(800, 600))

        # load egg model
        panda = base.loader.loadModel('panda')
        panda.reparentTo(base.render)
        panda.setScale(10, 10, 10)
        panda.setPos(0, 500, -50)

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

App().run()

As I understand, the most important thing to get it working is to reparent main Panda direct window to WX top widget (e.g. Frame).

Sorry, Elkav, that I use your topic to post this, but I think this is related to your post.

Hi maxxim

I played with your code snippet and found it to be very promising. Especially it solves one of the problems I encountered using my code snippet: FOV (Field Of View) could not be changed ([url]WxPandaWindow - setPov do nothing])

With best regards