"""DIRECT Animation Control Panel"""
__all__ = ['AnimPanel', 'ActorControl']
### SEE END OF FILE FOR EXAMPLE USEAGE ###
# Import Tkinter, Pmw, and the floater code from this directory tree.
from direct.tkwidgets.AppShell import *
from direct.showbase.TkGlobal import *
import Pmw, sys, os
from direct.task import Task
from panda3d.core import Filename, getModelPath
if sys.version_info >= (3, 0):
from tkinter.simpledialog import askfloat
from tkinter.filedialog import askopenfilename
else:
from tkSimpleDialog import askfloat
from tkFileDialog import askopenfilename
FRAMES = 0
SECONDS = 1
[docs]class AnimPanel(AppShell):
# Override class variables
appname = 'Anim Panel'
frameWidth = 675
frameHeight = 250
usecommandarea = 0
usestatusarea = 0
index = 0
[docs] def __init__(self, aList = [], parent = None, session = None, **kw):
INITOPT = Pmw.INITOPT
if isinstance(aList, (list, tuple)):
kw['actorList'] = aList
else:
kw['actorList'] = [aList]
optiondefs = (
('title', self.appname, None),
('actorList', [], None),
('Actor_label_width', 12, None),
)
self.defineoptions(kw, optiondefs)
# direct session that spawned me, if any, used
# for certain interactions with the session such
# as being able to see selected objects/actors
self.session = session
self.frameHeight = 60 + (50 * len(self['actorList']))
self.playList = []
self.id = 'AnimPanel_%d' % AnimPanel.index
AnimPanel.index += 1
# current index used for creating new actor controls
self.actorControlIndex = 0
# Initialize the superclass
AppShell.__init__(self)
# Execute option callbacks
self.initialiseoptions(AnimPanel)
# We need to know when AnimPanel is closed
self.destroyCallBack = None
[docs] def createInterface(self):
# Handle to the toplevels interior
interior = self.interior()
menuBar = self.menuBar
menuBar.addmenu('AnimPanel', 'Anim Panel Operations')
# Actor control status
menuBar.addcascademenu('AnimPanel', 'Control Status',
'Enable/disable actor control panels')
menuBar.addmenuitem('Control Status', 'command',
'Enable all actor controls',
label = 'Enable all',
command = self.enableActorControls)
menuBar.addmenuitem('Control Status', 'command',
'Disable all actor controls',
label = 'Disable all',
command = self.disableActorControls)
# Frame Slider units
menuBar.addcascademenu('AnimPanel', 'Display Units',
'Select display units')
menuBar.addmenuitem('Display Units', 'command',
'Display frame counts', label = 'Frame count',
command = self.displayFrameCounts)
menuBar.addmenuitem('Display Units', 'command',
'Display seconds', label = 'Seconds',
command = self.displaySeconds)
# Reset all actor controls
menuBar.addmenuitem('AnimPanel', 'command',
'Set actor controls to t = 0.0',
label = 'Jump all to zero',
command = self.resetAllToZero)
menuBar.addmenuitem('AnimPanel', 'command',
'Set Actor controls to end time',
label = 'Jump all to end time',
command = self.resetAllToEnd)
# Add some buttons to update all Actor Controls
self.fToggleAll = 1
b = self.createcomponent(
'toggleEnableButton', (), None,
Button, (self.menuFrame,),
text = 'Toggle Enable',
command = self.toggleAllControls)
b.pack(side = RIGHT, expand = 0)
b = self.createcomponent(
'showSecondsButton', (), None,
Button, (self.menuFrame,),
text = 'Show Seconds',
command = self.displaySeconds)
b.pack(side = RIGHT, expand = 0)
b = self.createcomponent(
'showFramesButton', (), None,
Button, (self.menuFrame,),
text = 'Show Frames',
command = self.displayFrameCounts)
b.pack(side = RIGHT, expand = 0)
self.actorFrame = None
self.createActorControls()
# Create a frame to hold the playback controls
controlFrame = Frame(interior)
self.toStartButton = self.createcomponent(
'toStart', (), None,
Button, (controlFrame,),
text = '<<',
width = 4,
command = self.resetAllToZero)
self.toStartButton.pack(side = LEFT, expand = 1, fill = X)
self.toPreviousFrameButton = self.createcomponent(
'toPreviousFrame', (), None,
Button, (controlFrame,),
text = '<',
width = 4,
command = self.previousFrame)
self.toPreviousFrameButton.pack(side = LEFT, expand = 1, fill = X)
self.playButton = self.createcomponent(
'playButton', (), None,
Button, (controlFrame,),
text = 'Play', width = 8,
command = self.playActorControls)
self.playButton.pack(side = LEFT, expand = 1, fill = X)
self.stopButton = self.createcomponent(
'stopButton', (), None,
Button, (controlFrame,),
text = 'Stop', width = 8,
command = self.stopActorControls)
self.stopButton.pack(side = LEFT, expand = 1, fill = X)
self.toNextFrameButton = self.createcomponent(
'toNextFrame', (), None,
Button, (controlFrame,),
text = '>',
width = 4,
command = self.nextFrame)
self.toNextFrameButton.pack(side = LEFT, expand = 1, fill = X)
self.toEndButton = self.createcomponent(
'toEnd', (), None,
Button, (controlFrame,),
text = '>>',
width = 4,
command = self.resetAllToEnd)
self.toEndButton.pack(side = LEFT, expand = 1, fill = X)
self.loopVar = IntVar()
self.loopVar.set(0)
self.loopButton = self.createcomponent(
'loopButton', (), None,
Checkbutton, (controlFrame,),
text = 'Loop', width = 8,
variable = self.loopVar)
self.loopButton.pack(side = LEFT, expand = 1, fill = X)
# add actors and animations, only allowed if a direct
# session has been specified since these currently require
# interaction with selected objects
if (self.session):
menuBar.addmenuitem('File', 'command',
'Set currently selected group of objects as actors to animate.',
label = 'Set Actors',
command = self.setActors)
menuBar.addmenuitem('File', 'command',
'Load animation file',
label = 'Load Anim',
command = self.loadAnim)
controlFrame.pack(fill = X)
[docs] def createActorControls(self):
# Create a frame to hold all the actor controls
self.actorFrame = Frame(self.interior())
# Create a control for each actor
self.actorControlList = []
for actor in self['actorList']:
anims = actor.getAnimNames()
print("actor animnames: %s"%anims)
topAnims = []
if 'neutral' in anims:
i = anims.index('neutral')
del(anims[i])
topAnims.append('neutral')
if 'walk' in anims:
i = anims.index('walk')
del(anims[i])
topAnims.append('walk')
if 'run' in anims:
i = anims.index('run')
del(anims[i])
topAnims.append('run')
anims.sort()
anims = topAnims + anims
if (len(anims)== 0):
# no animations set for this actor, don't
# display the control panel
continue
# currComponents = self.components()
# if ('actorControl%d' % index in currComponents):
# self.destroycomponent('actorControl%d' % index)
# ac = self.component('actorControl%d' % index)
# if (ac == None):
ac = self.createcomponent(
'actorControl%d' % self.actorControlIndex, (), 'Actor',
ActorControl, (self.actorFrame,),
animPanel = self,
text = actor.getName(),
animList = anims,
actor = actor)
ac.pack(expand = 1, fill = X)
self.actorControlList.append(ac)
self.actorControlIndex = self.actorControlIndex + 1
# Now pack the actor frame
self.actorFrame.pack(expand = 1, fill = BOTH)
[docs] def clearActorControls(self):
if (self.actorFrame):
self.actorFrame.forget()
self.actorFrame.destroy()
self.actorFrame = None
[docs] def setActors(self):
self.stopActorControls()
actors = self.session.getSelectedActors()
# make sure selected objects are actors, if not don't
# use?
aList = []
for currActor in actors:
aList.append(currActor)
self['actorList'] = aList
self.clearActorControls()
self.createActorControls()
[docs] def loadAnim(self):
# bring up file open box to allow selection of an
# animation file
animFilename = askopenfilename(
defaultextension = '.mb',
filetypes = (('Maya Models', '*.mb'),
('All files', '*')),
initialdir = '/i/beta',
title = 'Load Animation',
parent = self.component('hull')
)
if not animFilename or animFilename == 'None':
# no file selected, canceled
return
# add directory where animation was loaded from to the
# current model path so any further searches for the file
# can find it
fileDirName = os.path.dirname(animFilename)
fileBaseName = os.path.basename(animFilename)
fileBaseNameBase = os.path.splitext(fileBaseName)[0]
fileDirNameFN = Filename(fileDirName)
fileDirNameFN.makeCanonical()
getModelPath().prependDirectory(fileDirNameFN)
for currActor in self['actorList']:
# replace all currently loaded anims with specified one
# currActor.unloadAnims(None, None, None)
currActor.loadAnims({fileBaseNameBase:fileBaseNameBase})
self.clearActorControls()
self.createActorControls()
[docs] def playActorControls(self):
self.stopActorControls()
self.lastT = globalClock.getFrameTime()
self.playList = self.actorControlList[:]
taskMgr.add(self.play, self.id + '_UpdateTask')
[docs] def play(self, task):
if not self.playList:
return Task.done
fLoop = self.loopVar.get()
currT = globalClock.getFrameTime()
deltaT = currT - self.lastT
self.lastT = currT
for actorControl in self.playList:
# scale time by play rate value
actorControl.play(deltaT * actorControl.playRate, fLoop)
return Task.cont
[docs] def stopActorControls(self):
taskMgr.remove(self.id + '_UpdateTask')
[docs] def getActorControlAt(self, index):
return self.actorControlList[index]
[docs] def enableActorControlAt(self, index):
self.getActorControlAt(index).enableControl()
[docs] def toggleAllControls(self):
if self.fToggleAll:
self.disableActorControls()
else:
self.enableActorControls()
self.fToggleAll = 1 - self.fToggleAll
[docs] def enableActorControls(self):
for actorControl in self.actorControlList:
actorControl.enableControl()
[docs] def disableActorControls(self):
for actorControl in self.actorControlList:
actorControl.disableControl()
[docs] def disableActorControlAt(self, index):
self.getActorControlAt(index).disableControl()
[docs] def displayFrameCounts(self):
for actorControl in self.actorControlList:
actorControl.displayFrameCounts()
[docs] def displaySeconds(self):
for actorControl in self.actorControlList:
actorControl.displaySeconds()
[docs] def resetAllToZero(self):
for actorControl in self.actorControlList:
actorControl.resetToZero()
[docs] def resetAllToEnd(self):
for actorControl in self.actorControlList:
actorControl.resetToEnd()
[docs] def nextFrame(self):
for actorControl in self.actorControlList:
actorControl.nextFrame()
[docs] def previousFrame(self):
for actorControl in self.actorControlList:
actorControl.previousFrame()
[docs] def setDestroyCallBack(self, callBack):
self.destroyCallBack = callBack
[docs] def destroy(self):
# First clean up
taskMgr.remove(self.id + '_UpdateTask')
if self.destroyCallBack is not None:
self.destroyCallBack()
self.destroyCallBack = None
AppShell.destroy(self)
[docs]class ActorControl(Pmw.MegaWidget):
[docs] def __init__(self, parent = None, **kw):
INITOPT = Pmw.INITOPT
DEFAULT_FONT = (('MS', 'Sans', 'Serif'), 12, 'bold')
DEFAULT_ANIMS = ('neutral', 'run', 'walk')
animList = kw.get('animList', DEFAULT_ANIMS)
if len(animList) > 0:
initActive = animList[0]
else:
initActive = DEFAULT_ANIMS[0]
optiondefs = (
('text', 'Actor', self._updateLabelText),
('animPanel', None, None),
('actor', None, None),
('animList', DEFAULT_ANIMS, None),
('active', initActive, None),
('sLabel_width', 5, None),
('sLabel_font', DEFAULT_FONT, None),
)
self.defineoptions(kw, optiondefs)
# Initialize the superclass
Pmw.MegaWidget.__init__(self, parent)
# Handle to the toplevels hull
interior = self.interior()
interior.configure(relief = RAISED, bd = 2)
# Instance variables
self.fps = 24
self.offset = 0.0
self.maxSeconds = 1.0
self.currT = 0.0
self.fScaleCommand = 0
self.fOneShot = 0
# Create component widgets
self._label = self.createcomponent(
'label', (), None,
Menubutton, (interior,),
font=('MSSansSerif', 14, 'bold'),
relief = RAISED, bd = 1,
activebackground = '#909090',
text = self['text'])
# Top level menu
labelMenu = Menu(self._label, tearoff = 0)
# Menu to select display mode
self.unitsVar = IntVar()
self.unitsVar.set(FRAMES)
displayMenu = Menu(labelMenu, tearoff = 0)
displayMenu.add_radiobutton(label = 'Frame count',
value = FRAMES,
variable = self.unitsVar,
command = self.updateDisplay)
displayMenu.add_radiobutton(label = 'Seconds',
value = SECONDS,
variable = self.unitsVar,
command = self.updateDisplay)
# Items for top level menu
labelMenu.add_cascade(label = 'Display Units', menu = displayMenu)
# labelMenu.add_command(label = 'Set Offset', command = self.setOffset)
labelMenu.add_command(label = 'Jump To Zero',
command = self.resetToZero)
labelMenu.add_command(label = 'Jump To End Time',
command = self.resetToEnd)
# Now associate menu with menubutton
self._label['menu'] = labelMenu
self._label.pack(side = LEFT, fill = X)
# Combo box to select current animation
self.animMenu = self.createcomponent(
'animMenu', (), None,
Pmw.ComboBox, (interior,),
labelpos = W, label_text = 'Anim:',
entry_width = 12, selectioncommand = self.selectAnimNamed,
scrolledlist_items = self['animList'])
self.animMenu.selectitem(self['active'])
self.animMenu.pack(side = 'left', padx = 5, expand = 0)
# Combo box to select frame rate
playRateList = ['1/24.0', '0.1', '0.5', '1.0', '2.0', '5.0', '10.0']
playRate = '%0.1f' % self['actor'].getPlayRate(self['active'])
if playRate not in playRateList:
playRateList.append(playRate)
playRateList.sort(key=lambda s:eval(s))
playRateMenu = self.createcomponent(
'playRateMenu', (), None,
Pmw.ComboBox, (interior,),
labelpos = W, label_text = 'Play Rate:',
entry_width = 4, selectioncommand = self.setPlayRate,
scrolledlist_items = playRateList)
playRateMenu.selectitem(playRate)
playRateMenu.pack(side = LEFT, padx = 5, expand = 0)
# Scale to control animation
frameFrame = Frame(interior, relief = SUNKEN, bd = 1)
self.minLabel = self.createcomponent(
'minLabel', (), 'sLabel',
Label, (frameFrame,),
text = 0)
self.minLabel.pack(side = LEFT)
self.frameControl = self.createcomponent(
'scale', (), None,
Scale, (frameFrame,),
from_ = 0, to = 24, resolution = 1.0,
command = self.goTo,
orient = HORIZONTAL, showvalue = 1)
self.frameControl.pack(side = LEFT, expand = 1)
self.frameControl.bind('<Button-1>', self.__onPress)
self.frameControl.bind('<ButtonRelease-1>', self.__onRelease)
self.maxLabel = self.createcomponent(
'maxLabel', (), 'sLabel',
Label, (frameFrame,),
text = 24)
self.maxLabel.pack(side = LEFT)
frameFrame.pack(side = LEFT, expand = 1, fill = X)
# Checkbutton to enable/disable control
self.frameActiveVar = IntVar()
self.frameActiveVar.set(1)
frameActive = self.createcomponent(
'checkbutton', (), None,
Checkbutton, (interior,),
variable = self.frameActiveVar)
frameActive.pack(side = LEFT, expand = 1)
# Execute option callbacks
self.initialiseoptions(ActorControl)
self.playRate = 1.0
self.updateDisplay()
def _updateLabelText(self):
self._label['text'] = self['text']
[docs] def updateDisplay(self):
actor = self['actor']
active = self['active']
self.fps = actor.getFrameRate(active)
if (self.fps == None):
# there was probably a problem loading the
# active animation, set default anim properties
print("unable to get animation fps, zeroing out animation info")
self.fps = 24
self.duration = 0
self.maxFrame = 0
self.maxSeconds = 0
else:
self.duration = actor.getDuration(active)
self.maxFrame = actor.getNumFrames(active) - 1
self.maxSeconds = self.offset + self.duration
# switch between showing frame counts and seconds
if self.unitsVar.get() == FRAMES:
# these are approximate due to discrete frame size
fromFrame = 0
toFrame = self.maxFrame
self.minLabel['text'] = fromFrame
self.maxLabel['text'] = toFrame
self.frameControl.configure(from_ = fromFrame,
to = toFrame,
resolution = 1.0)
else:
self.minLabel['text'] = '0.0'
self.maxLabel['text'] = "%.2f" % self.duration
self.frameControl.configure(from_ = 0.0,
to = self.duration,
resolution = 0.01)
def __onPress(self, event):
# Enable slider command
self.fScaleCommand = 1
def __onRelease(self, event):
# Disable slider command
self.fScaleCommand = 0
[docs] def selectAnimNamed(self, name):
# Update active anim
self['active'] = name
# Reset play rate
self.component('playRateMenu').selectitem('1.0')
self.setPlayRate('1.0')
# Move slider to zero
self.resetToZero()
[docs] def setPlayRate(self, rate):
# set play rate on the actor, although for the AnimPanel
# purpose we don't use the actor's play rate, but rather
# the self.playRate value since we drive the animation
# playback ourselves
self['actor'].setPlayRate(eval(rate), self['active'])
self.playRate = eval(rate)
self.updateDisplay()
[docs] def setOffset(self):
newOffset = askfloat(parent = self.interior(),
title = self['text'],
prompt = 'Start offset (seconds):')
if newOffset != None:
self.offset = newOffset
self.updateDisplay()
[docs] def enableControl(self):
self.frameActiveVar.set(1)
[docs] def disableControl(self):
self.frameActiveVar.set(0)
[docs] def displayFrameCounts(self):
self.unitsVar.set(FRAMES)
self.updateDisplay()
[docs] def displaySeconds(self):
self.unitsVar.set(SECONDS)
self.updateDisplay()
[docs] def play(self, deltaT, fLoop):
if self.frameActiveVar.get():
# Compute new time
self.currT = self.currT + deltaT
if fLoop and self.duration:
# If its looping compute modulo
loopT = self.currT % self.duration
self.goToT(loopT)
else:
if (self.currT > self.maxSeconds):
# Clear this actor control from play list
self['animPanel'].playList.remove(self)
else:
self.goToT(self.currT)
else:
# Clear this actor control from play list
self['animPanel'].playList.remove(self)
[docs] def goToF(self, f):
if self.unitsVar.get() == FRAMES:
self.frameControl.set(f)
else:
self.frameControl.set(f/self.fps)
[docs] def goToT(self, t):
if self.unitsVar.get() == FRAMES:
self.frameControl.set(t * self.fps)
else:
self.frameControl.set(t)
[docs] def goTo(self, t):
# Convert scale value to float
t = float(t)
# Now convert t to seconds for offset calculations
if self.unitsVar.get() == FRAMES:
t = t / self.fps
# Update currT
if self.fScaleCommand or self.fOneShot:
self.currT = t
self.fOneShot = 0
# Now update actor (pose specifed as frame count)
self['actor'].pose(self['active'],
min(self.maxFrame, int(t * self.fps)))
[docs] def resetToZero(self):
# This flag forces self.currT to be updated to new value
self.fOneShot = 1
self.goToT(0)
[docs] def resetToEnd(self):
# This flag forces self.currT to be updated to new value
self.fOneShot = 1
self.goToT(self.duration)
[docs] def nextFrame(self):
"""
There needed to be a better way to select an exact frame number
as the control slider doesn't have the desired resolution
"""
self.fOneShot = 1
self.goToT((self.currT+(1/self.fps))%self.duration)
[docs] def previousFrame(self):
"""
There needed to be a better way to select an exact frame number
as the control slider doesn't have the desired resolution
"""
self.fOneShot = 1
self.goToT((self.currT-(1/self.fps))%self.duration)
"""
# EXAMPLE CODE
from direct.actor import Actor
import AnimPanel
a = Actor.Actor({250:{"head":"phase_3/models/char/dogMM_Shorts-head-250",
"torso":"phase_3/models/char/dogMM_Shorts-torso-250",
"legs":"phase_3/models/char/dogMM_Shorts-legs-250"},
500:{"head":"phase_3/models/char/dogMM_Shorts-head-500",
"torso":"phase_3/models/char/dogMM_Shorts-torso-500",
"legs":"phase_3/models/char/dogMM_Shorts-legs-500"},
1000:{"head":"phase_3/models/char/dogMM_Shorts-head-1000",
"torso":"phase_3/models/char/dogMM_Shorts-torso-1000",
"legs":"phase_3/models/char/dogMM_Shorts-legs-1000"}},
{"head":{"walk":"phase_3/models/char/dogMM_Shorts-head-walk", \
"run":"phase_3/models/char/dogMM_Shorts-head-run"}, \
"torso":{"walk":"phase_3/models/char/dogMM_Shorts-torso-walk", \
"run":"phase_3/models/char/dogMM_Shorts-torso-run"}, \
"legs":{"walk":"phase_3/models/char/dogMM_Shorts-legs-walk", \
"run":"phase_3/models/char/dogMM_Shorts-legs-run"}})
a.attach("head", "torso", "joint-head", 250)
a.attach("torso", "legs", "joint-hips", 250)
a.attach("head", "torso", "joint-head", 500)
a.attach("torso", "legs", "joint-hips", 500)
a.attach("head", "torso", "joint-head", 1000)
a.attach("torso", "legs", "joint-hips", 1000)
a.drawInFront("joint-pupil?", "eyes*", -1, lodName=250)
a.drawInFront("joint-pupil?", "eyes*", -1, lodName=500)
a.drawInFront("joint-pupil?", "eyes*", -1, lodName=1000)
a.setLOD(250, 250, 75)
a.setLOD(500, 75, 15)
a.setLOD(1000, 15, 1)
a.fixBounds()
a.reparentTo(render)
a2 = Actor.Actor({250:{"head":"phase_3/models/char/dogMM_Shorts-head-250",
"torso":"phase_3/models/char/dogMM_Shorts-torso-250",
"legs":"phase_3/models/char/dogMM_Shorts-legs-250"},
500:{"head":"phase_3/models/char/dogMM_Shorts-head-500",
"torso":"phase_3/models/char/dogMM_Shorts-torso-500",
"legs":"phase_3/models/char/dogMM_Shorts-legs-500"},
1000:{"head":"phase_3/models/char/dogMM_Shorts-head-1000",
"torso":"phase_3/models/char/dogMM_Shorts-torso-1000",
"legs":"phase_3/models/char/dogMM_Shorts-legs-1000"}},
{"head":{"walk":"phase_3/models/char/dogMM_Shorts-head-walk", \
"run":"phase_3/models/char/dogMM_Shorts-head-run"}, \
"torso":{"walk":"phase_3/models/char/dogMM_Shorts-torso-walk", \
"run":"phase_3/models/char/dogMM_Shorts-torso-run"}, \
"legs":{"walk":"phase_3/models/char/dogMM_Shorts-legs-walk", \
"run":"phase_3/models/char/dogMM_Shorts-legs-run"}})
a2.attach("head", "torso", "joint-head", 250)
a2.attach("torso", "legs", "joint-hips", 250)
a2.attach("head", "torso", "joint-head", 500)
a2.attach("torso", "legs", "joint-hips", 500)
a2.attach("head", "torso", "joint-head", 1000)
a2.attach("torso", "legs", "joint-hips", 1000)
a2.drawInFront("joint-pupil?", "eyes*", -1, lodName=250)
a2.drawInFront("joint-pupil?", "eyes*", -1, lodName=500)
a2.drawInFront("joint-pupil?", "eyes*", -1, lodName=1000)
a2.setLOD(250, 250, 75)
a2.setLOD(500, 75, 15)
a2.setLOD(1000, 15, 1)
a2.fixBounds()
a2.reparentTo(render)
ap = AnimPanel.AnimPanel([a, a2])
# Alternately
ap = a.animPanel()
ap2 = a2.animPanel()
"""