animation.py 6.8 KB

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