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 *
from direct.showbase.TkGlobal import *
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


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 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 = 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 = 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 = 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: def strCmp(a, b): return cmp(eval(a), eval(b)) playRateList.append(playRate) playRateList.sort(strCmp) 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 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() """