File: animation.py

package info (click to toggle)
grass 8.4.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 277,040 kB
  • sloc: ansic: 460,798; python: 227,732; cpp: 42,026; sh: 11,262; makefile: 7,007; xml: 3,637; sql: 968; lex: 520; javascript: 484; yacc: 450; asm: 387; perl: 157; sed: 25; objc: 6; ruby: 4
file content (230 lines) | stat: -rw-r--r-- 6,964 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
"""
@package nviz.animation

@brief Nviz (3D view) animation

Classes:
 - animation::Animation

(C) 2011 by the GRASS Development Team

This program is free software under the GNU General Public License
(>=v2). Read the file COPYING that comes with GRASS for details.

@author Anna Kratochvilova <kratochanna gmail.com>
"""

import os
import copy

import wx

from grass.pydispatch.signal import Signal


class Animation:
    """Class represents animation as a sequence of states (views).
    It enables to record, replay the sequence and finally generate
    all image files. Recording and replaying is based on timer events.
    There is no frame interpolation like in the Tcl/Tk based Nviz.
    """

    def __init__(self, mapWindow, timer):
        """Animation constructor

        Signals:
            animationFinished - emitted when animation finished
                              - attribute 'mode'
            animationUpdateIndex - emitted during animation to update gui
                                 - attributes 'index' and 'mode'

        :param mapWindow: glWindow where rendering takes place
        :param timer: timer for recording and replaying
        """
        self.animationFinished = Signal("Animation.animationFinished")
        self.animationUpdateIndex = Signal("Animation.animationUpdateIndex")

        self.animationList = []  # view states
        self.timer = timer
        self.mapWindow = mapWindow
        self.actions = {"record": self.Record, "play": self.Play}
        self.formats = ["tif", "ppm"]  # currently supported formats
        self.mode = "record"  # current mode (record, play, save)
        self.paused = False  # recording/replaying paused
        self.currentFrame = 0  # index of current frame
        self.fps = 24  # user settings   # Frames per second

        self.stopSaving = False  # stop during saving images
        self.animationSaved = False  # current animation saved or not

    def Start(self):
        """Start recording/playing"""
        self.timer.Start(int(self.GetInterval()))

    def Pause(self):
        """Pause recording/playing"""
        self.timer.Stop()

    def Stop(self):
        """Stop recording/playing"""
        self.timer.Stop()
        self.PostFinishedEvent()

    def Update(self):
        """Record/play next view state (on timer event)"""
        self.actions[self.mode]()

    def Record(self):
        """Record new view state"""
        self.animationList.append(
            {
                "view": copy.deepcopy(self.mapWindow.view),
                "iview": copy.deepcopy(self.mapWindow.iview),
            }
        )
        self.currentFrame += 1
        self.PostUpdateIndexEvent(index=self.currentFrame)
        self.animationSaved = False

    def Play(self):
        """Render next frame"""
        if not self.animationList:
            self.Stop()
            return
        try:
            self.IterAnimation()
        except IndexError:
            # no more frames
            self.Stop()

    def IterAnimation(self):
        params = self.animationList[self.currentFrame]
        self.UpdateView(params)
        self.currentFrame += 1

        self.PostUpdateIndexEvent(index=self.currentFrame)

    def UpdateView(self, params):
        """Update view data in map window and render"""
        toolWin = self.mapWindow.GetToolWin()
        toolWin.UpdateState(view=params["view"], iview=params["iview"])

        self.mapWindow.UpdateView()

        self.mapWindow.render["quick"] = True
        self.mapWindow.Refresh(False)

    def IsRunning(self):
        """Test if timer is running"""
        return self.timer.IsRunning()

    def SetMode(self, mode):
        """Start animation mode

        :param mode: animation mode (record, play, save)
        """
        self.mode = mode

    def GetMode(self):
        """Get animation mode (record, play, save)"""
        return self.mode

    def IsPaused(self):
        """Test if animation is paused"""
        return self.paused

    def SetPause(self, pause):
        self.paused = pause

    def Exists(self):
        """Returns if an animation has been recorded"""
        return bool(self.animationList)

    def GetFrameCount(self):
        """Return number of recorded frames"""
        return len(self.animationList)

    def Clear(self):
        """Clear all records"""
        self.animationList = []
        self.currentFrame = 0

    def GoToFrame(self, index):
        """Render frame of given index"""
        if index >= len(self.animationList):
            return

        self.currentFrame = index
        params = self.animationList[self.currentFrame]
        self.UpdateView(params)

    def PostFinishedEvent(self):
        """Animation ends"""
        self.animationFinished.emit(mode=self.mode)

    def PostUpdateIndexEvent(self, index):
        """Frame index changed, update tool window"""
        self.animationUpdateIndex(index=index, mode=self.mode)

    def StopSaving(self):
        """Abort image files generation"""
        self.stopSaving = True

    def IsSaved(self):
        """Test if animation has been saved (to images)"""
        return self.animationSaved

    def SaveAnimationFile(self, path, prefix, format):
        """Generate image files

        :param path: path to directory
        :param prefix: file prefix
        :param format: index of image file format
        """
        size = self.mapWindow.GetClientSize()
        toolWin = self.mapWindow.GetToolWin()

        formatter = ":04.0f"
        n = len(self.animationList)
        if n < 10:
            formatter = ":01.0f"
        elif n < 100:
            formatter = ":02.0f"
        elif n < 1000:
            formatter = ":03.0f"

        self.currentFrame = 0
        self.mode = "save"
        for params in self.animationList:
            if not self.stopSaving:
                self.UpdateView(params)
                number = ("{frame" + formatter + "}").format(frame=self.currentFrame)
                filename = "{prefix}_{number}.{ext}".format(
                    prefix=prefix, number=number, ext=self.formats[format]
                )
                filepath = os.path.join(path, filename)
                self.mapWindow.SaveToFile(
                    FileName=filepath,
                    FileType=self.formats[format],
                    width=size[0],
                    height=size[1],
                )
                self.currentFrame += 1

                wx.GetApp().Yield()
                toolWin.UpdateFrameIndex(index=self.currentFrame, goToFrame=False)
            else:
                self.stopSaving = False
                break
        self.animationSaved = True
        self.PostFinishedEvent()

    def SetFPS(self, fps):
        """Set Frames Per Second value
        :param fps: frames per second
        """
        self.fps = fps

    def GetInterval(self):
        """Return timer interval in ms"""
        return 1000.0 / self.fps