mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-04 02:42:49 -04:00
609 lines
25 KiB
Python
609 lines
25 KiB
Python
#################################################################
|
|
# collisionWindow.py
|
|
# Written by Yi-Hong Lin, yihhongl@andrew.cmu.edu, 2004
|
|
#################################################################
|
|
# Import Tkinter, Pmw, and the floater code from this directory tree.
|
|
from direct.tkwidgets.AppShell import *
|
|
from direct.showbase.TkGlobal import *
|
|
from tkSimpleDialog import askfloat
|
|
import string
|
|
import math
|
|
import types
|
|
from direct.task import Task
|
|
|
|
FRAMES = 0
|
|
SECONDS = 1
|
|
|
|
class AnimPanel(AppShell):
|
|
#################################################################
|
|
# This class will generate a animation panel for an actor
|
|
# which user assigned. Inside this panel, instead of using actorInterval
|
|
# or just simply calling the play function in Actor, we create a task to
|
|
# set animation frame by frame using setPose.
|
|
#################################################################
|
|
# Override class variables
|
|
appname = 'Anim Panel'
|
|
frameWidth = 575
|
|
frameHeight = 250
|
|
usecommandarea = 0
|
|
usestatusarea = 0
|
|
index = 0
|
|
dragMode = False
|
|
rateList= ['1/24.0', '0.1', '0.5', '1.0', '2.0', '5.0' , '10.0']
|
|
|
|
|
|
def __init__(self, aNode = None, parent = None, **kw):
|
|
INITOPT = Pmw.INITOPT
|
|
self.id = 'AnimPanel '+ aNode.getName()
|
|
self.appname = self.id
|
|
optiondefs = (
|
|
('title', self.appname, None),
|
|
('actor', aNode, None),
|
|
('animList', [], None),
|
|
)
|
|
self.defineoptions(kw, optiondefs)
|
|
|
|
self.frameHeight = 300
|
|
self.id = 'AnimPanel '+ aNode.getName()
|
|
self.nodeName = aNode.getName()
|
|
# Initialize the superclass
|
|
AppShell.__init__(self)
|
|
|
|
# Execute option callbacks
|
|
self.initialiseoptions(AnimPanel)
|
|
|
|
self.currTime = 0.0 # Initialize the start time
|
|
self.animName = None
|
|
|
|
self.parent.resizable(False,False) ## Disable the ability to resize for this Window.
|
|
|
|
def createInterface(self):
|
|
# Handle to the toplevels interior
|
|
interior = self.interior()
|
|
menuBar = self.menuBar
|
|
|
|
menuBar.addmenu('Anim', 'Anim Panel Operations')
|
|
|
|
# Reset all actor controls
|
|
menuBar.addmenuitem('File', 'command',
|
|
'Load Animation',
|
|
label = 'Load Animation',
|
|
command = self.loadAnimation)
|
|
menuBar.addmenuitem('Anim', 'command',
|
|
'Set actor controls to t = 0.0',
|
|
label = 'Jump all to zero',
|
|
command = self.resetAllToZero)
|
|
menuBar.addmenuitem('Anim', 'command',
|
|
'Set Actor controls to end time',
|
|
label = 'Jump all to end time',
|
|
command = self.resetAllToEnd)
|
|
menuBar.addmenuitem('Anim', 'separator')
|
|
menuBar.addmenuitem('Anim', 'command',
|
|
'Play Current Animation',
|
|
label = 'Play',
|
|
command = self.play)
|
|
menuBar.addmenuitem('Anim', 'command',
|
|
'Stop Current Animation',
|
|
label = 'stop',
|
|
command = self.stop)
|
|
|
|
# Create a frame to hold all the actor controls
|
|
actorFrame = Frame(interior)
|
|
name_label = Label(actorFrame, text= self.nodeName,font=('MSSansSerif', 16),
|
|
relief = SUNKEN, borderwidth=3)
|
|
name_label.place(x=5,y=5,anchor=NW)
|
|
Label(actorFrame, text= "Animation:", font=('MSSansSerif', 12)).place(x=140,y=5,anchor=NW)
|
|
Label(actorFrame, text= "Play Rate:", font=('MSSansSerif', 12)).place(x=140,y=35,anchor=NW)
|
|
self['animList'] = self['actor'].getAnimNames()
|
|
self.AnimEntry = self.createcomponent(
|
|
'AnimationMenu', (), None,
|
|
Pmw.ComboBox, (actorFrame,),
|
|
labelpos = W, entry_width = 20, selectioncommand = self.setAnimation,
|
|
scrolledlist_items = self['animList'])
|
|
self.AnimEntry.place(x=240,y=10,anchor=NW)
|
|
|
|
|
|
self.playRateEntry = self.createcomponent(
|
|
'playRateMenu', (), None,
|
|
Pmw.ComboBox, (actorFrame,),
|
|
labelpos = W, entry_width = 20, selectioncommand = self.setPlayRate,
|
|
scrolledlist_items = self.rateList)
|
|
self.playRateEntry.place(x=240,y=40,anchor=NW)
|
|
self.playRateEntry.selectitem('1.0')
|
|
|
|
### Loop checkbox
|
|
Label(actorFrame, text= "Loop:", font=('MSSansSerif', 12)).place(x=420,y=05,anchor=NW)
|
|
|
|
self.loopVar = IntVar()
|
|
self.loopVar.set(0)
|
|
self.loopButton = self.createcomponent(
|
|
'loopButton', (), None,
|
|
Checkbutton, (actorFrame,),
|
|
variable = self.loopVar)
|
|
self.loopButton.place(x=470,y=7,anchor=NW)
|
|
|
|
### Display Frames/Seconds
|
|
Label(actorFrame, text= "Frame/Second:", font=('MSSansSerif', 11)).place(x=5,y=75,anchor=NW)
|
|
|
|
self.unitsVar = IntVar()
|
|
self.unitsVar.set(FRAMES)
|
|
self.displayButton = self.createcomponent(
|
|
'displayButton', (), None,
|
|
Checkbutton, (actorFrame,),
|
|
command = self.updateDisplay,
|
|
variable = self.unitsVar)
|
|
self.displayButton.place(x=120,y=77,anchor=NW)
|
|
|
|
## scale control
|
|
frameFrame = Frame(actorFrame, 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, length = 500,
|
|
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)
|
|
|
|
## button contorl
|
|
ButtomFrame = Frame(actorFrame, relief = SUNKEN, bd = 1,borderwidth=5)
|
|
self.toStartButton = self.createcomponent(
|
|
'toStart', (), None,
|
|
Button, (ButtomFrame,),
|
|
text = '<<',
|
|
width = 8,
|
|
command = self.resetAllToZero)
|
|
self.toStartButton.pack(side = LEFT, expand = 1, fill = X)
|
|
|
|
self.playButton = self.createcomponent(
|
|
'playButton', (), None,
|
|
Button, (ButtomFrame,),
|
|
text = 'Play', width = 8,
|
|
command = self.play)
|
|
self.playButton.pack(side = LEFT, expand = 1, fill = X)
|
|
|
|
self.stopButton = self.createcomponent(
|
|
'stopButton', (), None,
|
|
Button, (ButtomFrame,),
|
|
text = 'Stop', width = 8, state=DISABLED,
|
|
command = self.stop)
|
|
self.stopButton.pack(side = LEFT, expand = 1, fill = X)
|
|
|
|
self.toEndButton = self.createcomponent(
|
|
'toEnd', (), None,
|
|
Button, (ButtomFrame,),
|
|
text = '>>',
|
|
width = 8,
|
|
command = self.resetAllToEnd)
|
|
self.toEndButton.pack(side = LEFT, expand = 1, fill = X)
|
|
|
|
ButtomFrame.place(anchor=NW,x=5,y=165)
|
|
|
|
self.removeButton = self.createcomponent(
|
|
'Remove Animation', (), None,
|
|
Button, (actorFrame,),
|
|
text = 'Remove This Animation', width = 20,
|
|
command = self.removeAnim)
|
|
self.removeButton.place(anchor=NW,x=5,y=220)
|
|
|
|
self.loadButton = self.createcomponent(
|
|
'Load Animation', (), None,
|
|
Button, (actorFrame,),
|
|
text = 'Load New Animation', width = 20,
|
|
command = self.loadAnimation)
|
|
self.loadButton.place(anchor=NW,x=180,y=220)
|
|
|
|
# Now pack the actor frame
|
|
actorFrame.pack(expand = 1, fill = BOTH)
|
|
|
|
def updateList(self):
|
|
#################################################################
|
|
# updateList(self)
|
|
# This function will update the list of animations that the Actor
|
|
# currently has into the combo box widget.
|
|
#################################################################
|
|
self.ignore('DataH_loadFinish'+self.nodeName)
|
|
del self.loaderWindow
|
|
self['animList'] = self['actor'].getAnimNames()
|
|
animL = self['actor'].getAnimNames()
|
|
self.AnimEntry.setlist(animL)
|
|
|
|
|
|
def removeAnim(self):
|
|
#################################################################
|
|
# removeAnim(self)
|
|
# This function will stop the animation and get the name of animation
|
|
# which user wish to remove from the panel. Then, it will send out
|
|
# a message to dataHolder to remove the target animation.
|
|
# And in the same time, it will start waiting a return message to
|
|
# make sure that target animation has been removed.
|
|
#################################################################
|
|
name = self.AnimEntry.get()
|
|
if taskMgr.hasTaskNamed(self.id + '_UpdateTask'):
|
|
self.stop()
|
|
self.accept('DataH_removeAnimFinish'+self.nodeName,self.afterRemove)
|
|
messenger.send('AW_removeAnim',[self['actor'],name])
|
|
return
|
|
|
|
def afterRemove(self):
|
|
#################################################################
|
|
# afterRemove(self)
|
|
# This function will be called once panel has received the return
|
|
# message from dataHolder. This function will call setList to
|
|
# reset the list of Animations
|
|
#################################################################
|
|
self.ignore('DataH_removeAnimFinish'+self.nodeName)
|
|
self['animList'] = self['actor'].getAnimNames()
|
|
animL = self['actor'].getAnimNames()
|
|
self.AnimEntry.setlist(animL)
|
|
print '-----',animL
|
|
return
|
|
|
|
def loadAnimation(self):
|
|
#################################################################
|
|
# loadAnimation(self)
|
|
# This function will open a dialog window to require user to input
|
|
# the animation he wants to load in for this actor.
|
|
#################################################################
|
|
self.loaderWindow = LoadAnimPanel(aNode=self['actor'])
|
|
self.accept('DataH_loadFinish'+self.nodeName,self.updateList)
|
|
return
|
|
|
|
def play(self):
|
|
#################################################################
|
|
# play(self)
|
|
# This function will be called when user click on the "play" button.
|
|
# First, this function will initialize all parameter that the actual
|
|
# play task need to run and add the play task into the taskMgr.
|
|
#################################################################
|
|
self.animName = self.AnimEntry.get()
|
|
if self.animName in self['animList']:
|
|
animName = self.AnimEntry.get()
|
|
self.playButton.config(state=DISABLED)
|
|
self.lastT = globalClock.getFrameTime()
|
|
taskMgr.add(self.playTask, self.id + '_UpdateTask')
|
|
self.stopButton.config(state=NORMAL)
|
|
else:
|
|
print '----Illegal Animaion name!!', self.animName
|
|
return
|
|
|
|
def playTask(self, task):
|
|
#################################################################
|
|
# playTask(self, task)
|
|
# This task will record time by each frame
|
|
# In fact it is just a clock keeper.
|
|
# If the current frame time over the max long of the animation,
|
|
# it will reset the timer.
|
|
# Anyway, this function will call gotoT by each frame.
|
|
#################################################################
|
|
fLoop = self.loopVar.get()
|
|
currT = globalClock.getFrameTime()
|
|
deltaT = currT - self.lastT
|
|
self.lastT = currT
|
|
if self.dragMode:
|
|
return Task.cont
|
|
self.currTime = self.currTime + deltaT
|
|
if (self.currTime > self.maxSeconds):
|
|
if fLoop:
|
|
self.currTime = self.currTime%self.duration
|
|
self.gotoT(self.currTime)
|
|
else:
|
|
self.currTime = 0.0
|
|
self.gotoT(0.0)
|
|
self.playButton.config(state=NORMAL)
|
|
self.stopButton.config(state=DISABLED)
|
|
return Task.done
|
|
else:
|
|
self.gotoT(self.currTime)
|
|
return Task.cont
|
|
|
|
def stop(self):
|
|
#################################################################
|
|
# stop(self)
|
|
# This function will remove the play task from taskMgr when user
|
|
# click on the "Stop" button
|
|
#################################################################
|
|
taskMgr.remove(self.id + '_UpdateTask')
|
|
self.playButton.config(state=NORMAL)
|
|
self.stopButton.config(state=DISABLED)
|
|
return
|
|
|
|
def setAnimation(self, animation):
|
|
#################################################################
|
|
# setAnimation(self, animation)
|
|
# This function will be called each time when user change
|
|
# the current animation. Most important thing this function do is
|
|
# to recalculate all variables to fit the selected animation
|
|
#################################################################
|
|
self.animName = self.AnimEntry.get()
|
|
playRate = '%0.1f' % self['actor'].getPlayRate(self.animName)
|
|
if playRate not in self.rateList:
|
|
def strCmp(a, b):
|
|
return cmp(eval(a), eval(b))
|
|
self.rateList.append(playRate)
|
|
self.rateList.sort(strCmp)
|
|
self.playRateEntry.reset(self.rateList)
|
|
self.playRateEntry.selectitem(playRate)
|
|
self.currTime = 0.0
|
|
self.frameControl.set(0)
|
|
self.updateDisplay()
|
|
return
|
|
|
|
def setPlayRate(self,rate):
|
|
#################################################################
|
|
# setPlayRate(self, rate)
|
|
# This function will be called each time when user changes the current play rate.
|
|
#################################################################
|
|
self.animName = self.AnimEntry.get()
|
|
if self.animName in self['animList']:
|
|
self['actor'].setPlayRate(eval(rate), self.animName)
|
|
self.updateDisplay()
|
|
return
|
|
|
|
def updateDisplay(self):
|
|
#################################################################
|
|
# updateDisplay(self)
|
|
# This function will be called whenever something has been changed
|
|
# on the panel. In here we will re-new all widgets on the panel to
|
|
# correct value.
|
|
#################################################################
|
|
self.fps = self['actor'].getFrameRate(self.animName)
|
|
self.duration = self['actor'].getDuration(self.animName)
|
|
self.maxFrame = self['actor'].getNumFrames(self.animName) - 1
|
|
self.maxSeconds = self.duration
|
|
if self.unitsVar.get() == FRAMES:
|
|
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 gotoT(self,time):
|
|
#################################################################
|
|
# gotoT(self, time)
|
|
# calculate the right parameter which will be send to set Frame
|
|
# Control slider, which is the real place we play the animation.
|
|
#################################################################
|
|
if self.unitsVar.get() == FRAMES:
|
|
self.frameControl.set(time * self.fps)
|
|
else:
|
|
self.frameControl.set(time)
|
|
return
|
|
|
|
def goTo(self,frame):
|
|
#################################################################
|
|
# goto(self, frame)
|
|
# Call back function for the frame control slider.
|
|
# This function will set the animation by the value on the slider.
|
|
#
|
|
# This function is the real function we "play" the animation.
|
|
#
|
|
#################################################################
|
|
if self.animName in self['animList']:
|
|
# Convert scale value to float
|
|
frame = string.atof(frame)
|
|
# Now convert t to seconds for offset calculations
|
|
if self.unitsVar.get() == FRAMES:
|
|
frame = frame / self.fps
|
|
if self.dragMode:
|
|
# If user is clicking on the slider and is draging the bar, reset the global timer.
|
|
self.currTime = frame
|
|
self['actor'].pose(self.animName,
|
|
min(self.maxFrame, int(frame * self.fps)))
|
|
return
|
|
|
|
def onRelease(self,frame):
|
|
#################################################################
|
|
# onRelease(self, frame)
|
|
# disable the dragMode when user releases the bar on the slider.
|
|
#################################################################
|
|
self.dragMode = False
|
|
return
|
|
|
|
def onPress(self,frame):
|
|
#################################################################
|
|
# onPress(self, frame)
|
|
# enable the dragMode when user press the bar on the slider.
|
|
#################################################################
|
|
self.dragMode = True
|
|
return
|
|
|
|
def resetAllToZero(self):
|
|
#################################################################
|
|
# resetAllToZero(self)
|
|
# reset the global timer to zero and also move the slider to zero.
|
|
# This will also reset the actor to the zero frame of current animation
|
|
#################################################################
|
|
self.currTime = 0.0
|
|
self.gotoT(0)
|
|
return
|
|
|
|
def resetAllToEnd(self):
|
|
#################################################################
|
|
# resetAllToEnd(self)
|
|
# set the global timer to the end of current animation and
|
|
# also move the slider to the end.
|
|
#################################################################
|
|
self.currTime = self.maxSeconds
|
|
self.gotoT(self.duration)
|
|
return
|
|
|
|
def onDestroy(self, event):
|
|
if taskMgr.hasTaskNamed(self.id + '_UpdateTask'):
|
|
taskMgr.remove(self.id + '_UpdateTask')
|
|
self.ignore('DataH_loadFinish')
|
|
messenger.send('AW_close',[self.nodeName])
|
|
'''
|
|
If you have open any thing, please rewrite here!
|
|
'''
|
|
pass
|
|
|
|
class LoadAnimPanel(AppShell):
|
|
#################################################################
|
|
# LoadAnimPanel(AppShell)
|
|
# This class will open a dialog to ask user to input names and
|
|
# file paths of animations
|
|
#################################################################
|
|
# Override class variables
|
|
appname = 'Load Animation'
|
|
frameWidth = 575
|
|
frameHeight = 200
|
|
usecommandarea = 0
|
|
usestatusarea = 0
|
|
index = 0
|
|
## Anim name : File Path
|
|
|
|
def __init__(self, aNode = None, parent = None, **kw):
|
|
INITOPT = Pmw.INITOPT
|
|
self.id = 'Load Animation '+ aNode.getName()
|
|
self.appname = self.id
|
|
self.animDic = {}
|
|
self.animList = []
|
|
optiondefs = (
|
|
('title', self.appname, None),
|
|
)
|
|
self.defineoptions(kw, optiondefs)
|
|
|
|
self.frameHeight = 300
|
|
self.nodeName = aNode.getName()
|
|
self.Actor = aNode
|
|
# Initialize the superclass
|
|
AppShell.__init__(self)
|
|
|
|
# Execute option callbacks
|
|
self.initialiseoptions(LoadAnimPanel)
|
|
|
|
def createInterface(self):
|
|
self.menuBar.destroy()
|
|
interior = self.interior()
|
|
mainFrame = Frame(interior)
|
|
self.inputZone = Pmw.Group(mainFrame, tag_text='File Setting')
|
|
self.inputZone.pack(fill='both',expand=1)
|
|
settingFrame = self.inputZone.interior()
|
|
Label(settingFrame,text='Anim Name').place(anchor=NW,x=60,y=5)
|
|
Label(settingFrame,text='File Path').place(anchor=NW,x=205,y=5)
|
|
self.AnimName_1 = self.createcomponent(
|
|
'Anim Name List', (), None,
|
|
Pmw.ComboBox, (settingFrame,),label_text='Anim :',
|
|
labelpos = W, entry_width = 10, selectioncommand = self.selectAnim,
|
|
scrolledlist_items = self.animList)
|
|
self.AnimFile_1 = Pmw.EntryField(settingFrame,value='')
|
|
self.AnimFile_1.component('entry').config(width=20)
|
|
self.AnimName_1.place(anchor=NW,x=10,y=25)
|
|
self.AnimFile_1.place(anchor=NW,x=140,y=25)
|
|
self.Browse_1 = self.createcomponent(
|
|
'File Browser1', (), None,
|
|
Button, (mainFrame,),
|
|
text = 'Browse...',
|
|
command = self.Browse_1)
|
|
self.Browse_1.place(anchor=NW,x=270,y=38)
|
|
|
|
self.addIntoButton = self.createcomponent(
|
|
'Load Add', (), None,
|
|
Button, (mainFrame,),
|
|
text = 'Add to Load',
|
|
command = self.addIntoList)
|
|
self.addIntoButton.place(anchor=NW,x=345,y=38)
|
|
|
|
att_label = Label(mainFrame, font=('MSSansSerif', 10),
|
|
text= "Attention! Animations won't be loaded in before you press the 'OK' button below!")
|
|
att_label.place(anchor=NW,x=10,y=80)
|
|
|
|
self.button_ok = Button(mainFrame, text="OK", command=self.ok_press,width=10)
|
|
self.button_ok.pack(fill=BOTH,expand=0,side=RIGHT)
|
|
|
|
mainFrame.pack(expand = 1, fill = BOTH)
|
|
|
|
|
|
|
|
def onDestroy(self, event):
|
|
messenger.send('AWL_close',[self.nodeName])
|
|
'''
|
|
If you have open any thing, please rewrite here!
|
|
'''
|
|
pass
|
|
|
|
def selectAnim(self,name):
|
|
#################################################################
|
|
# selectAnim(self, name)
|
|
# This function will be called if user select an animation on the list.
|
|
#################################################################
|
|
if name in self.animDic:
|
|
self.AnimFile_1.setvalue = self.animDic[name]
|
|
return
|
|
|
|
def Browse_1(self):
|
|
#################################################################
|
|
# Browse_1(self)
|
|
# when the browse button pused, this function will be called.
|
|
# Do nothing but open a file dialog for user to set the path to target file
|
|
# Then, set the path back to the entry on the panel.
|
|
#################################################################
|
|
AnimFilename = askopenfilename(
|
|
defaultextension = '.egg',
|
|
filetypes = (('Egg Files', '*.egg'),
|
|
('Bam Files', '*.bam'),
|
|
('All files', '*')),
|
|
initialdir = '.',
|
|
title = 'File Path for Anim 1',
|
|
parent = self.parent)
|
|
if AnimFilename:
|
|
self.AnimFile_1.setvalue(AnimFilename)
|
|
return
|
|
|
|
def addIntoList(self):
|
|
#################################################################
|
|
# addIntoList(self)
|
|
# This function will be called each time when user click on the
|
|
# "Add to Load" button. This function will read the current data
|
|
# on the panel into a dictionary. then reset the list of the animation
|
|
# name list on this panel.(not the one in the animation panel...)
|
|
#
|
|
# This function won't load any animation....
|
|
#
|
|
#################################################################
|
|
name = self.AnimName_1.get()
|
|
self.animDic[name] = Filename.fromOsSpecific(self.AnimFile_1.getvalue()).getFullpath()
|
|
if name in self.animList:
|
|
pass
|
|
else:
|
|
self.animList.append(name)
|
|
self.AnimName_1.setlist(self.animList)
|
|
print self.animDic
|
|
return
|
|
|
|
def ok_press(self):
|
|
#################################################################
|
|
# ok_press(Self)
|
|
# This functiion will be called when user click on the "OK"
|
|
# button. This function will send a message along with the animation
|
|
# file we wish to load for this actor.
|
|
# Then, it will close the panel itself.
|
|
#################################################################
|
|
messenger.send('AW_AnimationLoad',[self.Actor,self.animDic])
|
|
#print self.animDic
|
|
self.quit()
|
|
return
|