wxPython + PyOgre = wxPyOgre

Fosk

16-03-2006 10:27:15

Hello,

I had promised a few weeks ago that I would post a demo class of how to use PyOgre with wxPython. I should have known better and not have made any promises until it was finished.. Well, here it is now.

I am very new to Ogre/PyOgre so this was for me pretty much of an exercice de style in order to learn PyOgre. Almost all I did came down to adapting snippets from various turorials.

Here is how it looks like :



Under is the class itself. As I said, I am a beginner so, any advices, comments, critics etc are warmly welcomed. Maybe, if enough people make modifications to improve this base class, it could event be worthy of publishing the result on the wiki.



import wx
import pyogre.ogre as ogre
try :
import psyco #use psyco if available (JIT compiler)
psyco.full()
except ImportError:
pass


class Struct:
"simple dummy class to regroup scene entities in a single parameter"
pass

class OgreWindow(wx.PyWindow):
"""wx.Frame subclass to embed an Ogre window in wxPython
It is somewhat inspired from SampleFramework.py"""

#all animation states included in this dictionary will be automatically updated at each render
animStates = {} #the values have to be (AnimationState, SpeedFactor) tuples

#namespace for holding a reference to the entities you create. You can comment it out
#if you don't like the idea
sceneEntities = Struct()

def __init__(self, parent, ID, size = wx.Size(640,480), renderSystem = "OpenGL",**kwargs):
self.parent=parent
wx.PyWindow.__init__(self, parent, ID, size = size, **kwargs)
#Event bindings
self.Bind(wx.EVT_CLOSE, self._OnCloseWindow)
self.Bind(wx.EVT_ERASE_BACKGROUND, self._OnEraseBackground)
self.Bind(wx.EVT_SIZE, self._OnSize)
self.Bind(wx.EVT_TIMER, self.UpdateRender) #Bind the timer events to Ogre rendering
#Timer creation
self.timer = wx.Timer()
self.timer.SetOwner(self) #Sets the timer to notify self: binding the timer event is not enough

#Ogre Initialisation
self._OgreInit(size,renderSystem)
self.SceneInitialisation()
self.UpdateRender()
self.SetFocus()#Gives KeyboardFocus

def _OgreInit(self,size,renderSystem):
#Root creation
root = ogre.Root(ogre.getPluginPath())
self.ogreRoot = root

# setup resources
config = ogre.ConfigFile()
config.loadFromFile('resources.cfg')
for section, key, path in config.values:
ogre.ResourceGroupManager.getSingleton().addResourceLocation(path, key, section)

#The following section tries to avoid showing the configuration dilaog.
rsList = root.getAvailableRenderers() #Gets the available RenderSystems
rs=None
CouldNotFindRequestedRenderer=False
for i in rsList:
if renderSystem in i.name: #Tries to locate the requested render system
rs=i
if rs is not None :
root.renderSystem=rs
rs.setConfigOption("Full Screen","No")
try :
rs.setConfigOption("Video Mode","800 x 600 @ 16-bit colour")
except :
CouldNotFindRequestedRenderer=True
else :
CouldNotFindRequestedRenderer=True

if CouldNotFindRequestedRenderer: #Calls Ogre's default Config dialog if failed
carryOn = root.showConfigDialog()
if not carryOn:
sys.exit('Quit from Config Dialog')

root.initialise(False)

renderParameters = ogre._StringStringMap()
renderParameters['externalWindowHandle'] = str(self.GetHandle())
renderWindow = root.createRenderWindow('wxPython render window', size[0],
size[1], False, renderParameters)
renderWindow.active = True
self.renderWindow = renderWindow
ogre.ResourceGroupManager.getSingleton().initialiseAllResourceGroups()




def _OnSize(self, event):

if getattr(self, 'ogreRoot', None):
self.UpdateRender()
event.Skip()

def _OnEraseBackground(self, event):
pass # Do nothing, to avoid flashing on MSW.

def _OnCloseWindow(self, event):
self.Destroy()

def AcceptsFocus(self):
return True


def StartRendering(self,interval=33):
"Activates the timer. A rendering will be triggered at each interval (default :33ms)"
self.timer.Start(interval)

def OnFrameStarted(self,event):
"SubClass and fill in this declaration if you want something done before each rendering"
pass

def OnFrameEnded(self,event):
"SubClass and fill in this declaration if you want something done after each rendering"
pass

def UpdateRender(self,event=None):
"""Method that can be called manually or by a wx.Timer
If the method is called by timer, the animation states contained in
the self.animStates variable get updated before rendering the frame"""

#Enables the user to define actions to be undertaken before the frame is started
self.OnFrameStarted(event)

if hasattr(event ,"GetInterval"): #Timer Events have a GetInterval method that returns the time interval between events
for anim,speed in self.animStates.itervalues():
anim.addTime(speed*event.GetInterval()/1000.0) #updates the animations

self.ogreRoot.renderOneFrame()#Asks Ogre to render a frame

#Enables the user to define actions to be undertaken after the frame has been drawn
self.OnFrameEnded(event)

def SceneInitialisation(self):
"""This method can be replaced completely to suit your needs or you can
subclass only the helper methods such as self._CreateCamera"""
self._ChooseSceneManager()
self._CreateCamera()
self._CreateViewport()
self._PopulateScene()
self._MouseAndKeysBindings()

#You will want to subclass the following methods to suit your needs

def _ChooseSceneManager(self):
"choose SceneManager"
sceneManager = self.ogreRoot.getSceneManager(ogre.ST_GENERIC)
self.sceneManager = sceneManager

def _CreateCamera(self):
"create a Camera"
camera = self.sceneManager.createCamera('Camera')
camera.lookAt(ogre.Vector3(0, 0, 0))
camera.nearClipDistance = 5
self.sceneEntities.Camera = camera

# create the camera nodes & attach camera
cameraNode = self.sceneManager.rootSceneNode.createChildSceneNode("CamNode", (0, 0, 250))
PitchNode = cameraNode.createChildSceneNode("PitchNode")
PitchNode.attachObject(camera)

self.sceneEntities.CamNode = cameraNode
self.sceneEntities.PitchNode = PitchNode

def _CreateViewport(self):
"create a Viewport"
renderWindow=self.renderWindow
viewport = renderWindow.addViewport(self.sceneEntities.Camera, 0, 0.0, 0.0, 1.0, 1.0)
viewport.backgroundColour = (0, 0, 0)



def _PopulateScene(self):
"Implement this method to put entities in your scene"
pass

def _MouseAndKeysBindings(self):
"Some Additional mouse and keys bindings"
d=10.0 #displacement for key strokes
self.ControlKeyDict={wx.WXK_LEFT:ogre.Vector3(-d,0.0,0.0),
wx.WXK_RIGHT:ogre.Vector3(d,0.0,0.0),
wx.WXK_UP:ogre.Vector3(0.0,0.0,-d),
wx.WXK_DOWN:ogre.Vector3(0.0,0.0,d),
wx.WXK_PAGEUP:ogre.Vector3(0.0,d,0.0),
wx.WXK_PAGEDOWN:ogre.Vector3(0.0,-d,0.0)}
self.Bind(wx.EVT_KEY_DOWN,self._DefaultKeyDownManager)
##self.Bind(wx.EVT_ENTER_WINDOW,lambda evt : self.SetFocus())
self.Bind(wx.EVT_MOUSE_EVENTS,self._DefaultMouseEventManager)

def _DefaultKeyDownManager(self,event):
"If you want to implement a similar callback function, do not forget the event.Skip() at the end"
validMove = self.ControlKeyDict.get(event.m_keyCode,False)
if validMove :
self.sceneEntities.CamNode.translate(validMove,self.sceneEntities.CamNode.TS_LOCAL )
event.Skip()

def _DefaultMouseEventManager(self, event):
"If you want to implement a similar callback function, do not forget the event.Skip() at the end"
self.SetFocus() #Gives Keyboard focus to the window

if event.RightDown(): #Precedes dragging
self.StartDragX, self.StartDragY = event.GetPosition() #saves position of initial click

if event.Dragging() and event.RightIsDown(): #Dragging with RMB
x,y = event.GetPosition()
dx = x-self.StartDragX
dy = y-self.StartDragY
self.StartDragX, self.StartDragY = x, y

self.sceneEntities.CamNode.yaw(ogre.Degree(dx/3.0))
self.sceneEntities.PitchNode.pitch(ogre.Degree(dy/3.0))


event.Skip()




and if you want to test it and see the same result as in the picture above, you can add the following :


#Below is an example of the class in use

if __name__=="__main__":
import wx.py as py #This modules gives access to the pyCrust interpreter widget

class DemoOgreWindow(OgreWindow):
"""This is a demo of how to subclass OgreWindow.
Many parts of the code are directely inspired from the beginner to
advance tutorials"""

fishIsSwiming=False #A variable to keep track whether the fish is swiming or not

def _PopulateScene(self):
"Creation of scene objects"
# create scene
sceneManager=self.sceneManager
sceneManager.ambientLight = 0.2, 0.2, 0.2
sceneManager.setSkyDome(True, 'Examples/CloudySky', 4.0, 8.0)

light = sceneManager.createLight('MainLight')
light.position = (20, 80, 130)
self.sceneEntities.MainLight = light

# add some fog
sceneManager.setFog(ogre.FOG_EXP, ogre.ColourValue.White, 0.0002)

plane = ogre.Plane()
plane.normal = ogre.Vector3(0, 1, 0)
plane.d = 200
ogre.MeshManager.getSingleton().createPlane('FloorPlane', "General",
plane, 200000, 200000,
20, 20, True, 1, 50, 50,
(0, 0, 1))

# create floor entity
entity = sceneManager.createEntity('floor', 'FloorPlane')
entity.setMaterialName('Examples/RustySteel')
sceneManager.rootSceneNode.createChildSceneNode().attachObject(entity)
self.sceneEntities.floor = entity


# create fish entity
fishNode = sceneManager.rootSceneNode.createChildSceneNode("fishNode")
entity = sceneManager.createEntity('Wanda', 'fish.mesh')
fishNode.attachObject(entity)
fishNode.scale = (5.0,5.0,5.0)

self.sceneEntities.fishNode = fishNode
self.sceneEntities.fish = entity
self.swimAnimationState = entity.getAnimationState('swim')
self.swimAnimationState.enabled = True



#create fish swiming path
self.sceneEntities.fishAnimationNode = fishAnimationNode = sceneManager.rootSceneNode.createChildSceneNode("fishAnimationNode")
animation = sceneManager.createAnimation('fishTrack', 10)
animation.interpolationMode = ogre.Animation.IM_SPLINE
animationTrack = animation.createTrack(0, fishAnimationNode)
key = animationTrack.createKeyFrame(0.0)
key = animationTrack.createKeyFrame(1.0)
key.translation = (80.0, 0.0, -40.0)
key = animationTrack.createKeyFrame(2.0)
key.translation = (120.0, 0.0, 0.0)
key = animationTrack.createKeyFrame(3.0)
key.translation = (80.0, 0.0, 80.0)
key = animationTrack.createKeyFrame(5.0)
key.translation = (-80.0, 0.0, -40.0)
key = animationTrack.createKeyFrame(6.0)
key.translation = (-120.0, 0.0, 0.0)
key = animationTrack.createKeyFrame(7.0)
key.translation = (-80.0, 0.0, 100.0)
key = animationTrack.createKeyFrame(8.0)
key.translation = (30.0, 0.0, 90.0)
key = animationTrack.createKeyFrame(9.0)
key.translation = (-20.0, 0.0, 40.0)
key = animationTrack.createKeyFrame(10.0)
key.translation = (0.0, 0.0, 0.0)

self.fishAnimationState = sceneManager.createAnimationState('fishTrack')
self.fishAnimationState.enabled = True

#create a duplicate fish swiming path for determining where the fish has to look to
self.sceneEntities.fishLookAtNode = fishLookAtNode = sceneManager.rootSceneNode.createChildSceneNode("fishLookAtNode")
animation = sceneManager.createAnimation('fishLookAtTrack', 10)
animation.interpolationMode = ogre.Animation.IM_SPLINE
animationTrack = animation.createTrack(0, fishLookAtNode)
key = animationTrack.createKeyFrame(0.0)
key = animationTrack.createKeyFrame(1.0)
key.translation = (80.0, 0.0, -40.0)
key = animationTrack.createKeyFrame(2.0)
key.translation = (120.0, 0.0, 0.0)
key = animationTrack.createKeyFrame(3.0)
key.translation = (80.0, 0.0, 80.0)
key = animationTrack.createKeyFrame(5.0)
key.translation = (-80.0, 0.0, -40.0)
key = animationTrack.createKeyFrame(6.0)
key.translation = (-120.0, 0.0, 0.0)
key = animationTrack.createKeyFrame(7.0)
key.translation = (-80.0, 0.0, 100.0)
key = animationTrack.createKeyFrame(8.0)
key.translation = (30.0, 0.0, 90.0)
key = animationTrack.createKeyFrame(9.0)
key.translation = (-20.0, 0.0, 40.0)
key = animationTrack.createKeyFrame(10.0)
key.translation = (0.0, 0.0, 0.0)
self.fishLookAtAnimationState = sceneManager.createAnimationState('fishLookAtTrack')
self.fishLookAtAnimationState.enabled = True
self.fishLookAtAnimationState.addTime(0.033) #Gives a 33ms headstart to the lookat node


def OnFrameStarted(self,event):
"In this example, we will make the fish swim along a path"
if self.fishIsSwiming :
#First : updates the position of the fish to the position of the moving node on the animation path
#The reason that we did not create an animationTrack for fishNode is that we are interested in
#keyframing only the node position, not the scale, orientation etc...
self.sceneEntities.fishNode.position = self.sceneEntities.fishAnimationNode.position

#Then, takes care of the direction that the fish has to face to
directionToGo = self.sceneEntities.fishLookAtNode.position - self.sceneEntities.fishAnimationNode.position
src = self.sceneEntities.fishNode.orientation * (-ogre.Vector3.UNIT_X) #the fish is originally
#facing the negative X direction


quat = src.getRotationTo(directionToGo) #Calculates the quaternion of the appropriate rotation
self.sceneEntities.fishNode.rotate(quat)#Rotates, the fish. Here, we know that no rotation of 180
#degrees will occur so we do not have to care about it



def ToggleSwimAnimation(self):
"Toggles on and off the animation"
self.fishIsSwiming = not self.fishIsSwiming

if self.fishIsSwiming : #Adds the animation states for use
self.animStates["fishAnimation"] = (self.fishAnimationState, 1.0)
self.animStates["fishLookAtAnimation"] = (self.fishLookAtAnimationState, 1.0)
self.animStates["swimAnimation"] = (self.swimAnimationState, 4)
else : #Removes animation states
self.animStates.pop("fishAnimation")
self.animStates.pop("fishLookAtAnimation")
self.animStates.pop("swimAnimation")

class DemoFrame(wx.Frame):
"""Creates a frame with an Ogre window and a pyCrust window
pyCrust is always nice to have around for study/debug purposes"""
def __init__(self, *args, **kwds):
kwds["style"] = wx.DEFAULT_FRAME_STYLE
wx.Frame.__init__(self, *args, **kwds)
self.pyCrust = wx.py.crust.Crust(self, -1)
self.ogreWin = DemoOgreWindow(self, -1)
self.swimButton = wx.Button(self, -1, "Start/Stop Swiming")
self.trackButton = wx.ToggleButton(self, -1, "Camera Auto Tracking")
self.__set_properties()
self.__do_layout()
self.Bind(wx.EVT_BUTTON, self.swimButtonCallback, self.swimButton)
self.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleAutoTracking, self.trackButton)




def __set_properties(self):
self.SetTitle("wxPyOgre Demo")
self.ogreWin.SetMinSize((640,480))
self.trackButton.SetValue(False)

def __do_layout(self):
sizer_1 = wx.BoxSizer(wx.VERTICAL)
sizer_1.Add(self.ogreWin, 2, wx.EXPAND, 0)
sizer_1.Add(self.swimButton,0, wx.ALIGN_CENTER, 0)
sizer_1.Add(self.trackButton,0, wx.ALIGN_CENTER, 0)
sizer_1.Add(self.pyCrust, 1, wx.EXPAND, 0)
self.SetAutoLayout(True)
self.SetSizer(sizer_1)
sizer_1.Fit(self)
sizer_1.SetSizeHints(self)
self.Layout()

def swimButtonCallback(self,event):
self.ogreWin.ToggleSwimAnimation()

def ToggleAutoTracking(self,event):
"Toggles on and off the automatic tracking of the fish by the camera"
self.ogreWin.sceneEntities.Camera.setAutoTracking(event.Checked(), self.ogreWin.sceneEntities.fishNode)



DemoApp = wx.PySimpleApp(0)
wx.InitAllImageHandlers() #you may or may not need this
MainFrame = DemoFrame(None, -1, "")

#put all the entities in self.ogreWin.sceneEntities in the local variables so that
#introspection with pyCrust is more user friendly
localVariableDic=locals()
localVariableDic.update(MainFrame.ogreWin.sceneEntities.__dict__)

DemoApp.SetTopWindow(MainFrame)
MainFrame.ogreWin.StartRendering()
MainFrame.Show()

DemoApp.MainLoop()



Looking forward to your comments,

Fosk

Biggles

16-03-2006 12:26:43

Awesome, dude, works really well.

dermont

16-03-2006 20:19:10

Really nice demo.

deficite

20-03-2006 00:52:00

Thanks for this! Using your example I finally figured out how to get PyOgre to work with PyGame.

Fosk

20-03-2006 10:22:43

You need to thank the original coder of the wxdemo.py, that's where I took the handle stuff from.

Game_Ender

24-03-2006 06:57:06

What platforms has this been tested on?

Fosk

24-03-2006 09:56:25

Developed under WinXp and only tested with it. I have a Suze 10.0 laptop but I haven't installed PyOgre on it so I don't know if it works...

Is your question out of curiosity or have you been experimenting problems ?

Game_Ender

29-03-2006 22:40:27

It involves the way wxWidgets implements the GetHandle() function on each platform. I am not sure if it provides the exact right handle the Ogre needs on linux and mac. I remember interfacing wxWidgets with OpenSceneGraph on lnux using GetHandle. I had to find the proper GTK function to pull out the X11 handle because it returns the GTK handle. So I guess it really boils down to the Ogre platform manger and what it expects vs. what the GetHandle returns. If I find the spare time I will dig into the docs and Ogre source code/try it on Ubuntu to see if it works.

It would be awesome to be able to make some Ogre tools in wxPython.

hiasl

04-06-2006 17:20:21

Hi,

as Game_Ender expected, the Frame.GetHandle() seems to fail on Linux since the program crashes when it tries to create the RenderWindow.
Was anyone successful in running this demo on Linux?

Bye,
Matthias

Fosk

04-06-2006 23:53:26

Sorry, I have never used Linux for game developpment...and up until now, I had had almost no need for windows handle so I don't know how it is done in GTK

Wxwindows/GTK Guru anyone ?

Game_Ender

18-06-2006 20:49:57

Alright after some source digging (I love ViewCVS) I have found out what wxPython does with the GetHandle() function. In normal wxGTK you get a GdkWindow pointer, but in wxPython there is some glue code that gives you the xid instead. This means in C++ you need the following code: Ogre::NameValuePairList params;
String handle;
#ifdef __WXMSW__
handle = Ogre::StringConverter::toString((size_t)((HWND)GetHandle()));
#elif defined(__WXGTK__)
// TODO: Someone test this. you might to use "parentWindowHandle" if this
// does not work. Ogre 1.2 + Linux + GLX platform wants a string of the
// format display:screen:window, which has variable types ulong:uint:ulong.
GdkWindow * window = GetHandle()->window
handle = Ogre::StringConverter::toString((ulong)GDK_WINDOW_XDISPLAY(window));
handle += ":0:";
handle += Ogre::StringConverter::toString((uint)GDK_WINDOW_XID(window));
#else
#error Not supported on this platform.
#endif
params["externalWindowHandle"] = handle;


Now in wxPython there are two ways to go about this. The first is hacking the wxPython source to return a tuple with the xdisplay, xid, and screen. Now I wanted to use pygtk use that xid and get the needed xdisplay and screen information but you can't. So now we have two options, you need to use C++ to make this work. This means you have to patch pyOgre or Ogre itself to better handle this whole window handle concept on linux. A third is finding a python interface to X.

You will have to make some assumptions about the display and screen, but you can make some Xlib code that gets the Display* and Screen number Ogre needs. Probably using XOpenDisplay(), followed by XDefaultScreen, on the display you just got. I am not and expert on X and I don't have the time to test, but hopefully this will help somebody.

Game_Ender

20-06-2006 05:29:43

So I have modified the code the in OgreGLXWindow.cpp to use the default xdisplay and screen if you only pass in one parameter: the XID of the window. Which is what wxPython returns. Currently this is only create a sub window. It is not actually embedded in the main window. I am going to try the C++ version soon, which explicity specifies the Display*, to see if that actually embeds the window.

hiasl

04-08-2006 16:32:03

So I have modified the code the in OgreGLXWindow.cpp to use the default xdisplay and screen if you only pass in one parameter: the XID of the window. Which is what wxPython returns. Currently this is only create a sub window. It is not actually embedded in the main window. I am going to try the C++ version soon, which explicity specifies the Display*, to see if that actually embeds the window.

Hi Game_Ender,

have you been successful with your tryings to embbed an ogre display into the wx Main Window? Could you post your version of OgreGLXWindow.cpp?

It would be very helpfull to me :)

Game_Ender

05-08-2006 04:43:56

I will post what changes I have but my X/GLX skills are lacking. I was basically trying to get all the needed information to embed the window using just the XID of the window (which is all wxPythong on GTK provides). It didn't work: the window showed up, but not embedded. I should be able to post my modified code in a day or to.

pix

25-10-2006 23:22:00

any progress on this Game_Ender?

my guess is that you are getting a new window because you are creating a new Display instance, assuming you do something like XOpenDisplay to find the default display. to get the window to embed i think you need to somehow get the pointer to the same Display instance that wxWidgets is using.

i get the feeling that the only way to do this is to hack wxPython to return the display pointer as an int.

just guessing though.

pix.

Khemka86

17-11-2009 06:36:00

I have some Confusion.

first of all, if you are attaching multiple nodes of fish in the scene and
each entities are having different animation state then how we will design our frame listener
for that . what are the things we should take care while writing frameStarted().

multiple Fish animation state might be complex. if we pick the robots and each are having different node position. if we want to anmate all of them in different path .how do we proceed ? how the animation is to b designed?

please clear my confusion.

Thnx in advance

Vinay K

Khemka86

07-12-2009 08:23:07

Dear,

I am also working in wxpython + pyogre. But i see there is some performance issue.The average fps is around 35-40fps. Whereas the same code using sampleframework is giving 400-500fps.

I am using wx.timer to update the renderer system.

Please guide me,what is the issue which is slowing my fps.

Thnx in advance

Vinay K