GetHandle() problem with wxPython on Linux

Hey all,

I’m having a problem getting my editor to work on Linux as I’m coming up against the GetHandle() returning 0 issue with wxPython. Nemesis has kindly offered a solution - showing the frame before initialising the viewports. This seems to work with the snippet attached below, but it doesn’t work with my editor source (here: github.com/Derfies/panda3d-editor) even though they’re doing pretty much the same thing.

I don’t have Linux so I can’t debug the issue. Is there anyone with a Linux machine who can take a look for me? I’d really appreciate it!

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

import wx 

from pandac.PandaModules import WindowProperties 
from direct.showbase.ShowBase import ShowBase 
from direct.showbase.DirectObject import DirectObject 


class Viewport( wx.Panel ): 
    
    def __init__( self, *args, **kwargs ): 
        """ 
        Initialise the wx panel. You must complete the other part of the 
        init process by calling Initialize() once the wx-window has been 
        built. 
        """ 
        wx.Panel.__init__( self, *args, **kwargs ) 
        
        self._win = None 
        
    def GetWindow( self ): 
        return self._win 
        
    def SetWindow( self, win ): 
        self._win = win 
        
    def Initialize( self, useMainWin=True ): 
        """ 
        The panda3d window must be put into the wx-window after it has been 
        shown, or it will not size correctly. 
        """ 
        assert self.GetHandle() != 0 
        wp = WindowProperties() 
        wp.setOrigin( 0, 0 ) 
        wp.setSize( self.ClientSize.GetWidth(), self.ClientSize.GetHeight() ) 
        wp.setParentWindow( self.GetHandle() ) 
        if self._win is None: 
            if useMainWin: 
                base.openDefaultWindow( props=wp, gsg=None ) 
                self._win = base.win 
            else: 
                self._win = base.openWindow( props=wp, makeCamera=0 ) 
        self.Bind( wx.EVT_SIZE, self.OnResize ) 
        
    def OnResize( self, event ): 
        """When the wx-panel is resized, fit the panda3d window into it.""" 
        frame_size = event.GetSize() 
        wp = WindowProperties() 
        wp.setOrigin( 0, 0 ) 
        wp.setSize( frame_size.GetWidth(), frame_size.GetHeight() ) 
        self._win.requestProperties( wp ) 
        

class App( wx.App, DirectObject ): 
    
    """ 
    Don't forget to bind your frame's wx.EVT_CLOSE event to the app's 
    self.Quit method, or the application will not close properly. 
    """ 
        
    def ReplaceEventLoop( self ): 
        self.evtLoop = wx.EventLoop() 
        self.oldLoop = wx.EventLoop.GetActive() 
        wx.EventLoop.SetActive( self.evtLoop ) 
        taskMgr.add( self.WxStep, 'evtLoopTask' ) 
        self.WxStep() 
        
    def OnDestroy( self, event=None ): 
        self.WxStep() 
        wx.EventLoop.SetActive( self.oldLoop ) 
        
    def Quit( self, event=None ): 
        self.OnDestroy( event ) 
        try: 
            base 
        except NameError: 
            sys.exit() 
        base.userExit() 
        
    def WxStep( self, task=None ): 
        while self.evtLoop.Pending(): 
            self.evtLoop.Dispatch() 
        self.ProcessIdle() 
        if task != None: 
            return task.cont 
        

class MainFrame( wx.Frame ): 
    
    """Panda Editor user interface.""" 

    def __init__( self, *args, **kwargs ): 
        wx.Frame.__init__( self, *args, **kwargs ) 
        
        self.vp = Viewport( self ) 
        self.SetTitle( 'wxWindow' ) 
        self.Bind( wx.EVT_CLOSE, self.OnClose ) 
        
        self.bs1 = wx.BoxSizer( wx.VERTICAL ) 
        self.bs1.Add( self.vp, 1, wx.EXPAND ) 
        self.SetSizer( self.bs1 ) 
        
    def OnClose( self, evt ): 
        self.Show( False ) 
        wx.GetApp().Quit() 
    

class App( App ): 
    
    """Base editor class.""" 
    
    def OnInit( self ): 
        self.frame = MainFrame( None, size=(800, 600) ) 
        ShowBase() 
        self.ReplaceEventLoop() 
        self.frame.Show() 
        wx.CallAfter( self.FinishInit ) 
        
        return True 
    
    def FinishInit( self ): 
        self.frame.vp.Initialize() 
        

app = App( redirect=False ) 
run()

First of all, sorry I couldn’t get to you on this earlier, mate. Life…

Now to the point. I don’t think this example you posted will help you. It works flawlessly, but the editor doesn’t. The bug must be specific to the editor, not to WX itself. I even tried running my editor backend inside WX, and it worked as well. So lets focus on your github code, or I could try and recreate the issue for you in some simpler sample. Assuming I find the time to do that, obviously.

Anyway, I took a couple of approaches at debugging this problem. What I know for now (which isn’t much, but it’s a start) is that when you comment the assertion out, the editor actually starts. And works (well, almost, some stuff isn’t visible, but that’s an entirely different issue).

When you do that, the editor window lands nicely inside the wx widget, while the game window ends up floating around. Same happens when you change the order of loading to:

wxEdWin.Initialize()
wxGameWin.Initialize(useMainWin=False)

However, if you ONLY set wxGameWin to userMainWin=False, you get the editor window embedded and no game window.

Whatever you do, the game window ALWAYS ends up with a 0 window id, while the editor window always ends up with the propper value.

I wish I had more for you. I’ll try to recreate the problem in a smaller scale sample. Maybe it’s because of tabs? I don’t know.

You should really get yourself a Linux install, mate. If it proves to be something really specific to your code, I doubt anyone else can debug it efficiently.

EDIT:
Looks like it really does have something to do with tabs. If you change the order in which tabs are added in mainFrame.py, the game window starts to work, and the editor window fails.

EDIT2:
Yeah, it really is the notebook. Comment out the assertion and add self.nbViewport.SetSelection(1) after this:

self.nbViewport.AddPage( self.pnlEdView, 'Editor' )
        self.nbViewport.AddPage( self.pnlGameView, 'Game' )

in mainFrame.py and you will reverse the effect (you’ll get the game window embedded and the editor window floating).

Coppertop, you’re a gentleman and a scholar. Sounds like yet another reason to abandon multiple viewports and just use display regions instead. Need to work out a decent way of moving the sash inbetween display regions however.

You can also try WxPandaWindow, if you’re running the buildbot version. This is a WxWidget that contains a Panda window, and it should work on Linux as well as Win and Mac (though I haven’t tested it a lot on Linux).

David

Thanks David. I’ve heard that using multiple wxPanels to host windows results in poor performance as it creates multiple rendering contexts. Is this something I should be worried about? I’ve just commited a change that moves everything to display regions and does away with the notebook altogether, so that might be enough for the immediate future. Maybe my editor will work on Linux now :smiley:

Some drivers do indeed seem to develop performance problems with multiple contexts (while others don’t), so for best compatibility it is usually a good idea to use multiple DisplayRegions within a single window when possible.

David