Hi all,
I am new to Panda3d and I am loving it. This is my first post as the forums have been really helpful in answering my questions.
I am building a network/graph viewer using wxPython and Panda3D and I already have a panda3d window embedded in a wxFrame inside wxPanel.
This is my question…
I see that even though my scene is static and unchanging, the program has relatively high CPU usage (40% or even more). This is because of calling taskMgr.step() periodically. It seems we are redrawing the scene even if nothing is changing.
However, as panda3d processes the mouse and keyboard in its client window area, I need to call taskMgr.step() to be responsive to mouse and key events…
Is there a way to tell panda whether to refresh the scene or not when taskMgr.step() is called? I would usually want taskMgr.step() to handle the mouse and keystrokes and return unless explicitly asked to refresh.
If not, is there a way to handle the mouse / keystrokes in wxPython so that I can decide outside panda3d when to call taskMgr.step(). I have tried to capture the wx events for mouse and keyboard for the wxPanel, but they don’t get called. I am guessing that the events go directly to the panda window which consumes them and they dont bubble up to the wxPython Panel. I see that there is a addPythonEventHandler on base.win… but its not clear to me what to pass it as arguments (probably because of my newbness rather than the lacking on part of the documentation).
Is there a way to make one of the above approaches work?
Much thanks…
I am also inserting the code to run my example…
NOTE: you would also need the square.egg model to be present in the same folder where you run this code… (the source for square.egg follows below the code)
import sys
import wx
#####################################################################
#####################################################################
# Panda module imports
#####################################################################
#####################################################################
from direct.showbase import DirectObject
from direct.gui.OnscreenText import OnscreenText
from direct.showbase.ShowBase import ShowBase
from direct.task.Task import Task
from panda3d.core import TextNode
from panda3d.core import AmbientLight,DirectionalLight,LightAttrib
from panda3d.core import Point3,Vec3,Vec4,BitMask32,Mat4
from panda3d.core import CollisionTraverser,CollisionNode
from panda3d.core import CollisionHandlerQueue,CollisionRay
from pandac.PandaModules import WindowProperties
#####################################################################
#####################################################################
# Common Definitions
#####################################################################
#####################################################################
BLACK = Vec4(0,0,0,1)
WHITE = Vec4(1,1,1,1)
HIGHLIGHT = Vec4(0,1,1,1)
NxN = 8
#####################################################################
#####################################################################
# Methods and classes
#####################################################################
#####################################################################
def SquarePos(i):
return Point3((i%NxN) - (NxN/2.0 - 0.5), int(i/NxN) - (NxN/2.0 - 0.5), 0)
#Helper function for determining wheter a square should be white or black
#The modulo operations (%) generate the every-other pattern of a chess-board
def SquareColor(i):
if (i + ((i/NxN)%2))%2: return BLACK
else: return WHITE
# Function to put instructions on the screen.
def addInstructions(pos, msg):
return OnscreenText(text=msg, style=1, fg=(1,1,1,1),
pos=(-1.3, pos), align=TextNode.ALeft, scale = .05)
# Function to put title on the screen.
def addTitle(text):
return OnscreenText(text=text, style=1, fg=(1,1,1,1),
pos=(1.3,-0.95), align=TextNode.ARight, scale = .07)
class PandaPanel(wx.Panel, DirectObject.DirectObject):
_label = 'Panda3D'
def __init__(self, parent, network, *args, **kwargs):
wx.Panel.__init__(self, parent, *args, **kwargs)
self.Bind(wx.EVT_SIZE, self.OnResize)
self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
self.Bind(wx.EVT_MOTION, self.OnMouseMove)
self.base = ShowBase()
self.network = network
self.initialize()
self.drawScene()
wx.CallLater(30, self.timedFunc)
self.count = 0
def OnKeyDown(self, event):
print 'on keypress'
def OnMouseMove(self, event):
print "on mouse move"
def timedFunc(self, *a, **k):
self.count += 1
try:
taskMgr.step()
except:
pass
wx.CallLater(30, self.timedFunc)
def initialize(self):
assert self.GetHandle() != 0
wp = WindowProperties()
wp.setOrigin(0,0)
w, h = self.GetParent().GetClientSizeTuple()
wp.setSize(w, h)
wp.setParentWindow(self.GetHandle())
self.base.openDefaultWindow(props = wp, gsg = None)
self.mouseMoveEventCount = 0
def OnResize(self, event):
frame_size = self.GetParent().GetClientSizeTuple()
wp = WindowProperties()
wp.setOrigin(0,0)
w, h = self.GetParent().GetClientSizeTuple()
wp.setParentWindow(self.GetHandle())
wp.setSize(w, h)
try:
self.base.win.requestProperties(wp)
except:
from traceback import print_exc
print 'error in requesting window properties'
print_exc()
self.Parent.Refresh()
def drawScene(self):
self.prepareGrid()
def prepareGrid(self):
self.title = addTitle("Panda3D Picking test")
self.accept('escape', sys.exit) #Escape quits
self.base.disableMouse()
camera.setPosHpr(0, -13.75, 6, 0, -25, 0)
self.setupLights()
self.picker = CollisionTraverser() #Make a traverser
self.pq = CollisionHandlerQueue() #Make a handler
self.pickerNode = CollisionNode('mouseRay')
self.pickerNP = camera.attachNewNode(self.pickerNode)
self.pickerNode.setFromCollideMask(BitMask32.bit(1))
self.pickerRay = CollisionRay() #Make our ray
self.pickerNode.addSolid(self.pickerRay) #Add it to the collision node
self.picker.addCollider(self.pickerNP, self.pq)
self.squareRoot = render.attachNewNode("squareRoot")
#For each square
self.squares = [None for i in range(NxN * NxN)]
for i in range(NxN*NxN):
#Load, parent, color, and position the model (a single square polygon)
self.squares[i] = self.base.loader.loadModel('square')
self.squares[i].reparentTo(self.squareRoot)
self.squares[i].setPos(SquarePos(i))
self.squares[i].setColor(SquareColor(i))
#Set the model itself to be collideable with the ray. If this model was
#any more complex than a single polygon, you should set up a collision
#sphere around it instead. But for single polygons this works fine.
self.squares[i].find("**/polygon").node().setIntoCollideMask(
BitMask32.bit(1))
#Set a tag on the square's node so we can look up what square this is
#later during the collision pass
self.squares[i].find("**/polygon").node().setTag('square', str(i))
self.mouseTask = taskMgr.add(self.mouseTask, 'mouseTask')
self.hiSq = False
def mouseTask(self, task):
#This task deals with the highlighting and dragging based on the mouse
#First, clear the current highlight
if self.hiSq is not False:
self.squares[self.hiSq].setColor(SquareColor(self.hiSq))
self.hiSq = False
#Check to see if we can access the mouse. We need it to do anything else
if base.mouseWatcherNode.hasMouse():
#get the mouse position
mpos = base.mouseWatcherNode.getMouse()
#Set the position of the ray based on the mouse position
self.pickerRay.setFromLens(base.camNode, mpos.getX(), mpos.getY())
#Do the actual collision pass (Do it only on the squares for
#efficiency purposes)
self.picker.traverse(self.squareRoot)
if self.pq.getNumEntries() > 0:
#if we have hit something, sort the hits so that the closest
#is first, and highlight that node
self.pq.sortEntries()
i = int(self.pq.getEntry(0).getIntoNode().getTag('square'))
#Set the highlight on the picked square
self.squares[i].setColor(HIGHLIGHT)
self.hiSq = i
return Task.cont
def setupLights(self): #This function sets up some default lighting
ambientLight = AmbientLight( "ambientLight" )
ambientLight.setColor( Vec4(.8, .8, .8, 1) )
directionalLight = DirectionalLight( "directionalLight" )
directionalLight.setDirection( Vec3( 0, 45, -45 ) )
directionalLight.setColor( Vec4( 0.2, 0.2, 0.2, 1 ) )
self.base.render.setLight(render.attachNewNode( directionalLight ) )
self.base.render.setLight(render.attachNewNode( ambientLight ) )
class PandaFrame(wx.Frame):
def __init__(self, *args, **kwargs):
title = kwargs.pop('title', '')
wx.Frame.__init__(self, title=title, *args, **kwargs)
self.Show(True)
self.pandapanel = PandaPanel(self, *args, **kwargs)
#self.pandapanel.initialize() # this is called from inside the panda panel init method
class PandaApp(wx.App):
def __init__(self, *args, **kw):
wx.App.__init__(self)
self.frame = PandaFrame(None, wx.ID_ANY, title='Panda App', *args, **kw)
self.frame.Bind(wx.EVT_CLOSE, self.quit)
def onDestroy(self, event=None):
self.Exit()
pass
def quit(self, event=None):
self.onDestroy(event)
try:
base
except NameError:
sys.exit()
base.userExit()
if __name__ == '__main__':
app = PandaApp(size=(600, 300))
app.MainLoop()
square.egg contents…
<CoordinateSystem> { Y-Up }
<Comment> {
"Handmade quad"
}
<Group> polygon {
<VertexPool> pPlaneShape1.verts {
<Vertex> 1 {
-0.5 0 -0.5
<Normal> { 0 0 -1 }
<UV> { 0 0 }
<RGBA> { 1 1 1 1 }
}
<Vertex> 2 {
-0.5 0 0.5
<Normal> { 0 0 -1 }
<UV> { 0 1 }
<RGBA> { 1 1 1 1 }
}
<Vertex> 3 {
0.5 0 -0.5
<Normal> { 0 0 -1 }
<UV> { 1 0 }
<RGBA> { 1 1 1 1 }
}
<Vertex> 4 {
0.5 0 0.5
<Normal> { 0 0 -1 }
<UV> { 1 1 }
<RGBA> { 1 1 1 1 }
}
}
<Polygon> {
<Normal> { 0 0 -1 }
<VertexRef> { 1 2 4 3 <Ref> { pPlaneShape1.verts } }
}
}