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.
and if you want to test it and see the same result as in the picture above, you can add the following :
Looking forward to your comments,
Fosk
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