Source code for direct.tkpanels.AnimPanel

"""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 panda3d.core import Filename, getModelPath, ClockObject
from direct.tkwidgets.AppShell import AppShell
from direct.task import Task
from direct.task.TaskManagerGlobal import taskMgr
from tkinter.simpledialog import askfloat
from tkinter.filedialog import askopenfilename
import Pmw
import os
import tkinter as tk


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, tk.Button, (self.menuFrame,), text = 'Toggle Enable', command = self.toggleAllControls) b.pack(side = tk.RIGHT, expand = 0) b = self.createcomponent( 'showSecondsButton', (), None, tk.Button, (self.menuFrame,), text = 'Show Seconds', command = self.displaySeconds) b.pack(side = tk.RIGHT, expand = 0) b = self.createcomponent( 'showFramesButton', (), None, tk.Button, (self.menuFrame,), text = 'Show Frames', command = self.displayFrameCounts) b.pack(side = tk.RIGHT, expand = 0) self.actorFrame = None self.createActorControls() # Create a frame to hold the playback controls controlFrame = tk.Frame(interior) self.toStartButton = self.createcomponent( 'toStart', (), None, tk.Button, (controlFrame,), text = '<<', width = 4, command = self.resetAllToZero) self.toStartButton.pack(side = tk.LEFT, expand = 1, fill = tk.X) self.toPreviousFrameButton = self.createcomponent( 'toPreviousFrame', (), None, tk.Button, (controlFrame,), text = '<', width = 4, command = self.previousFrame) self.toPreviousFrameButton.pack(side = tk.LEFT, expand = 1, fill = tk.X) self.playButton = self.createcomponent( 'playButton', (), None, tk.Button, (controlFrame,), text = 'Play', width = 8, command = self.playActorControls) self.playButton.pack(side = tk.LEFT, expand = 1, fill = tk.X) self.stopButton = self.createcomponent( 'stopButton', (), None, tk.Button, (controlFrame,), text = 'Stop', width = 8, command = self.stopActorControls) self.stopButton.pack(side = tk.LEFT, expand = 1, fill = tk.X) self.toNextFrameButton = self.createcomponent( 'toNextFrame', (), None, tk.Button, (controlFrame,), text = '>', width = 4, command = self.nextFrame) self.toNextFrameButton.pack(side = tk.LEFT, expand = 1, fill = tk.X) self.toEndButton = self.createcomponent( 'toEnd', (), None, tk.Button, (controlFrame,), text = '>>', width = 4, command = self.resetAllToEnd) self.toEndButton.pack(side = tk.LEFT, expand = 1, fill = tk.X) self.loopVar = tk.IntVar() self.loopVar.set(0) self.loopButton = self.createcomponent( 'loopButton', (), None, tk.Checkbutton, (controlFrame,), text = 'Loop', width = 8, variable = self.loopVar) self.loopButton.pack(side = tk.LEFT, expand = 1, fill = tk.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 = tk.X)
[docs] def createActorControls(self): # Create a frame to hold all the actor controls self.actorFrame = tk.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 is 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 = tk.X) self.actorControlList.append(ac) self.actorControlIndex = self.actorControlIndex + 1 # Now pack the actor frame self.actorFrame.pack(expand = 1, fill = tk.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 = ClockObject.getGlobalClock().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 = ClockObject.getGlobalClock().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 = tk.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, tk.Menubutton, (interior,), font=('MSSansSerif', 14, 'bold'), relief = tk.RAISED, bd = 1, activebackground = '#909090', text = self['text']) # Top level menu labelMenu = tk.Menu(self._label, tearoff = 0) # Menu to select display mode self.unitsVar = tk.IntVar() self.unitsVar.set(FRAMES) displayMenu = tk.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 = tk.LEFT, fill = tk.X) # Combo box to select current animation self.animMenu = self.createcomponent( 'animMenu', (), None, Pmw.ComboBox, (interior,), labelpos = tk.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 = tk.W, label_text = 'Play Rate:', entry_width = 4, selectioncommand = self.setPlayRate, scrolledlist_items = playRateList) playRateMenu.selectitem(playRate) playRateMenu.pack(side = tk.LEFT, padx = 5, expand = 0) # Scale to control animation frameFrame = tk.Frame(interior, relief = tk.SUNKEN, bd = 1) self.minLabel = self.createcomponent( 'minLabel', (), 'sLabel', tk.Label, (frameFrame,), text = 0) self.minLabel.pack(side = tk.LEFT) self.frameControl = self.createcomponent( 'scale', (), None, tk.Scale, (frameFrame,), from_ = 0, to = 24, resolution = 1.0, command = self.goTo, orient = tk.HORIZONTAL, showvalue = 1) self.frameControl.pack(side = tk.LEFT, expand = 1) self.frameControl.bind('<Button-1>', self.__onPress) self.frameControl.bind('<ButtonRelease-1>', self.__onRelease) self.maxLabel = self.createcomponent( 'maxLabel', (), 'sLabel', tk.Label, (frameFrame,), text = 24) self.maxLabel.pack(side = tk.LEFT) frameFrame.pack(side = tk.LEFT, expand = 1, fill = tk.X) # Checkbutton to enable/disable control self.frameActiveVar = tk.IntVar() self.frameActiveVar.set(1) frameActive = self.createcomponent( 'checkbutton', (), None, tk.Checkbutton, (interior,), variable = self.frameActiveVar) frameActive.pack(side = tk.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 is 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 is not 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() """