animation.py 6.9 KB

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