animation.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. """
  2. @package nviz.animation
  3. @brief Nviz (3D view) animation
  4. Classes:
  5. - animation::Animation
  6. (C) 2011 by the GRASS Development Team
  7. This program is free software under the GNU General Public License
  8. (>=v2). Read the file COPYING that comes with GRASS for details.
  9. @author Anna Kratochvilova <kratochanna gmail.com>
  10. """
  11. import os
  12. import copy
  13. import wx
  14. from grass.pydispatch.signal import Signal
  15. from core.utils import _
  16. class Animation:
  17. """Class represents animation as a sequence of states (views).
  18. It enables to record, replay the sequence and finally generate
  19. all image files. Recording and replaying is based on timer events.
  20. There is no frame interpolation like in the Tcl/Tk based Nviz.
  21. """
  22. def __init__(self, mapWindow, timer):
  23. """Animation constructor
  24. Signals:
  25. animationFinished - emitted when animation finished
  26. - attribute 'mode'
  27. animationUpdateIndex - emitted during animation to update gui
  28. - attributes 'index' and 'mode'
  29. :param mapWindow: glWindow where rendering takes place
  30. :param timer: timer for recording and replaying
  31. """
  32. self.animationFinished = Signal('Animation.animationFinished')
  33. self.animationUpdateIndex = Signal('Animation.animationUpdateIndex')
  34. self.animationList = [] # view states
  35. self.timer = timer
  36. self.mapWindow = mapWindow
  37. self.actions = {'record': self.Record,
  38. 'play': self.Play}
  39. self.formats = ['tif', 'ppm'] # currently supported formats
  40. self.mode = 'record' # current mode (record, play, save)
  41. self.paused = False # recording/replaying paused
  42. self.currentFrame = 0 # index of current frame
  43. self.fps = 24 # user settings # Frames per second
  44. self.stopSaving = False # stop during saving images
  45. self.animationSaved = False # current animation saved or not
  46. def Start(self):
  47. """Start recording/playing"""
  48. self.timer.Start(self.GetInterval())
  49. def Pause(self):
  50. """Pause recording/playing"""
  51. self.timer.Stop()
  52. def Stop(self):
  53. """Stop recording/playing"""
  54. self.timer.Stop()
  55. self.PostFinishedEvent()
  56. def Update(self):
  57. """Record/play next view state (on timer event)"""
  58. self.actions[self.mode]()
  59. def Record(self):
  60. """Record new view state"""
  61. self.animationList.append(
  62. {'view': copy.deepcopy(self.mapWindow.view),
  63. 'iview': copy.deepcopy(self.mapWindow.iview)})
  64. self.currentFrame += 1
  65. self.PostUpdateIndexEvent(index=self.currentFrame)
  66. self.animationSaved = False
  67. def Play(self):
  68. """Render next frame"""
  69. if not self.animationList:
  70. self.Stop()
  71. return
  72. try:
  73. self.IterAnimation()
  74. except IndexError:
  75. # no more frames
  76. self.Stop()
  77. def IterAnimation(self):
  78. params = self.animationList[self.currentFrame]
  79. self.UpdateView(params)
  80. self.currentFrame += 1
  81. self.PostUpdateIndexEvent(index=self.currentFrame)
  82. def UpdateView(self, params):
  83. """Update view data in map window and render"""
  84. toolWin = self.mapWindow.GetToolWin()
  85. toolWin.UpdateState(view=params['view'], iview=params['iview'])
  86. self.mapWindow.UpdateView()
  87. self.mapWindow.render['quick'] = True
  88. self.mapWindow.Refresh(False)
  89. def IsRunning(self):
  90. """Test if timer is running"""
  91. return self.timer.IsRunning()
  92. def SetMode(self, mode):
  93. """Start animation mode
  94. :param mode: animation mode (record, play, save)
  95. """
  96. self.mode = mode
  97. def GetMode(self):
  98. """Get animation mode (record, play, save)"""
  99. return self.mode
  100. def IsPaused(self):
  101. """Test if animation is paused"""
  102. return self.paused
  103. def SetPause(self, pause):
  104. self.paused = pause
  105. def Exists(self):
  106. """Returns if an animation has been recorded"""
  107. return bool(self.animationList)
  108. def GetFrameCount(self):
  109. """Return number of recorded frames"""
  110. return len(self.animationList)
  111. def Clear(self):
  112. """Clear all records"""
  113. self.animationList = []
  114. self.currentFrame = 0
  115. def GoToFrame(self, index):
  116. """Render frame of given index"""
  117. if index >= len(self.animationList):
  118. return
  119. self.currentFrame = index
  120. params = self.animationList[self.currentFrame]
  121. self.UpdateView(params)
  122. def PostFinishedEvent(self):
  123. """Animation ends"""
  124. self.animationFinished.emit(mode=self.mode)
  125. def PostUpdateIndexEvent(self, index):
  126. """Frame index changed, update tool window"""
  127. self.animationUpdateIndex(index=index, mode=self.mode)
  128. def StopSaving(self):
  129. """Abort image files generation"""
  130. self.stopSaving = True
  131. def IsSaved(self):
  132. """"Test if animation has been saved (to images)"""
  133. return self.animationSaved
  134. def SaveAnimationFile(self, path, prefix, format):
  135. """Generate image files
  136. :param path: path to direcory
  137. :param prefix: file prefix
  138. :param format: index of image file format
  139. """
  140. w, h = self.mapWindow.GetClientSizeTuple()
  141. toolWin = self.mapWindow.GetToolWin()
  142. formatter = ':04.0f'
  143. n = len(self.animationList)
  144. if n < 10:
  145. formatter = ':01.0f'
  146. elif n < 100:
  147. formatter = ':02.0f'
  148. elif n < 1000:
  149. formatter = ':03.0f'
  150. self.currentFrame = 0
  151. self.mode = 'save'
  152. for params in self.animationList:
  153. if not self.stopSaving:
  154. self.UpdateView(params)
  155. number = (
  156. '{frame' +
  157. formatter +
  158. '}').format(
  159. frame=self.currentFrame)
  160. filename = "{prefix}_{number}.{ext}".format(
  161. prefix=prefix, number=number, ext=self.formats[format])
  162. filepath = os.path.join(path, filename)
  163. self.mapWindow.SaveToFile(
  164. FileName=filepath,
  165. FileType=self.formats[format],
  166. width=w,
  167. height=h)
  168. self.currentFrame += 1
  169. wx.Yield()
  170. toolWin.UpdateFrameIndex(
  171. index=self.currentFrame, goToFrame=False)
  172. else:
  173. self.stopSaving = False
  174. break
  175. self.animationSaved = True
  176. self.PostFinishedEvent()
  177. def SetFPS(self, fps):
  178. """Set Frames Per Second value
  179. :param fps: frames per second
  180. """
  181. self.fps = fps
  182. def GetInterval(self):
  183. """Return timer interval in ms"""
  184. return 1000. / self.fps