[GRASS-SVN] r49274 - grass/trunk/gui/wxpython/gui_modules

svn_grass at osgeo.org svn_grass at osgeo.org
Wed Nov 16 11:02:14 EST 2011


Author: annakrat
Date: 2011-11-16 08:02:14 -0800 (Wed, 16 Nov 2011)
New Revision: 49274

Added:
   grass/trunk/gui/wxpython/gui_modules/nviz_animation.py
Modified:
   grass/trunk/gui/wxpython/gui_modules/mapdisp.py
   grass/trunk/gui/wxpython/gui_modules/nviz_mapdisp.py
   grass/trunk/gui/wxpython/gui_modules/nviz_tools.py
   grass/trunk/gui/wxpython/gui_modules/preferences.py
   grass/trunk/gui/wxpython/gui_modules/workspace.py
   grass/trunk/gui/wxpython/gui_modules/wxnviz.py
Log:
wxNviz: simple animation implemented

Modified: grass/trunk/gui/wxpython/gui_modules/mapdisp.py
===================================================================
--- grass/trunk/gui/wxpython/gui_modules/mapdisp.py	2011-11-16 15:58:34 UTC (rev 49273)
+++ grass/trunk/gui/wxpython/gui_modules/mapdisp.py	2011-11-16 16:02:14 UTC (rev 49274)
@@ -649,7 +649,7 @@
             # add Nviz notebookpage
             self._layerManager.AddNvizTools()
             self.MapWindow3D.ResetViewHistory()
-            for page in ('view', 'light', 'fringe', 'constant', 'cplane'):
+            for page in ('view', 'light', 'fringe', 'constant', 'cplane', 'animation'):
                 self._layerManager.nviz.UpdatePage(page)
                 
         self.MapWindow3D.overlays = self.MapWindow2D.overlays

Added: grass/trunk/gui/wxpython/gui_modules/nviz_animation.py
===================================================================
--- grass/trunk/gui/wxpython/gui_modules/nviz_animation.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/gui_modules/nviz_animation.py	2011-11-16 16:02:14 UTC (rev 49274)
@@ -0,0 +1,208 @@
+"""!
+ at package nviz_animation.py
+
+ at brief Nviz (3D view) animation
+
+Classes:
+ - Animation
+
+(C) 2008-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.
+
+ at author Anna Kratochvilova <kratochanna gmail.com> 
+"""
+import os
+import copy
+
+import wx
+from wx.lib.newevent import NewEvent
+
+wxAnimationFinished, EVT_ANIM_FIN = NewEvent()
+wxAnimationUpdateIndex, EVT_ANIM_UPDATE_IDX = NewEvent()
+
+
+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
+        
+        @param mapWindow glWindow where rendering takes place
+        @param timer timer for recording and replaying
+        """
+        
+        self.animationList = []         # view states
+        self.timer = timer
+        self.mapWindow = mapWindow
+        self.actions = {'record': self.Record,
+                        'play': self.Play}
+        self.formats = ['ppm', 'tif']   # 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(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"""
+        toolWin = self.mapWindow.GetToolWin()
+        event = wxAnimationFinished(mode = self.mode)
+        wx.PostEvent(toolWin, event)
+        
+    def PostUpdateIndexEvent(self, index):
+        """!Frame index changed, update tool window"""
+        toolWin = self.mapWindow.GetToolWin()
+        event = wxAnimationUpdateIndex(index = index, mode = self.mode)
+        wx.PostEvent(toolWin, event)
+        
+    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 direcory
+        @param prefix file prefix
+        @param format index of image file format
+        """
+        w, h = self.mapWindow.GetClientSizeTuple()
+        toolWin = self.mapWindow.GetToolWin()
+        
+        self.currentFrame = 0
+        self.mode = 'save'
+        for params in self.animationList:
+            if not self.stopSaving:
+                self.UpdateView(params)
+                filename = prefix + "_" + str(self.currentFrame) + '.' + self.formats[format]
+                filepath = os.path.join(path, filename)
+                self.mapWindow.SaveToFile(FileName = filepath, FileType = self.formats[format],
+                                                  width = w, height = h)
+                self.currentFrame += 1
+                
+                wx.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. / self.fps

Modified: grass/trunk/gui/wxpython/gui_modules/nviz_mapdisp.py
===================================================================
--- grass/trunk/gui/wxpython/gui_modules/nviz_mapdisp.py	2011-11-16 15:58:34 UTC (rev 49273)
+++ grass/trunk/gui/wxpython/gui_modules/nviz_mapdisp.py	2011-11-16 16:02:14 UTC (rev 49274)
@@ -42,6 +42,7 @@
 from goutput        import wxCmdOutput
 from preferences    import globalSettings as UserSettings
 from workspace      import Nviz as NvizDefault
+from nviz_animation import Animation
 
 import wxnviz
 
@@ -119,6 +120,8 @@
         self.textdict = {}
         self.dragid = -1
         self.hitradius = 5
+        # layer manager toolwindow
+        self.toolWin = None        
     
         if self.lmgr:
             self.log = self.lmgr.goutput
@@ -158,6 +161,9 @@
         
         # timer for flythrough
         self.timerFly = wx.Timer(self, id = wx.NewId())
+        # timer for animations
+        self.timerAnim = wx.Timer(self, id = wx.NewId())
+        self.animation = Animation(mapWindow = self, timer = self.timerAnim)        
         
         self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
         self.Bind(wx.EVT_SIZE,             self.OnSize)
@@ -165,10 +171,11 @@
         self._bindMouseEvents()
         
         self.Bind(EVT_UPDATE_PROP,   self.UpdateMapObjProperties)
-        self.Bind(EVT_UPDATE_VIEW,   self.UpdateView)
+        self.Bind(EVT_UPDATE_VIEW,   self.OnUpdateView)
         self.Bind(EVT_UPDATE_LIGHT,  self.UpdateLight)
         self.Bind(EVT_UPDATE_CPLANE, self.UpdateCPlane)
         
+        self.Bind(wx.EVT_TIMER, self.OnTimerAnim, self.timerAnim)
         self.Bind(wx.EVT_TIMER, self.OnTimerFly, self.timerFly)
         self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
         self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
@@ -207,6 +214,7 @@
         self.ComputeFlyValues(mx = mx, my = my)
         self._display.FlyThrough(flyInfo = self.fly['value'], mode = self.fly['mode'],
                                  exagInfo = self.fly['exag'])
+        self.ChangeInnerView()                                 
         self.render['quick'] = True
         self.Refresh(False)
         
@@ -262,6 +270,7 @@
     
     def __del__(self):
         """!Stop timers if running, unload data"""
+        self.StopTimer(self.timerAnim)
         self.StopTimer(self.timerFly)
         self.UnloadDataLayers(force = True)
     
@@ -280,8 +289,17 @@
             cplane = copy.deepcopy(UserSettings.Get(group = 'nviz', key = 'cplane'))
             cplane['on'] = False
             self.cplanes.append(cplane)
+        
+    def SetToolWin(self, toolWin):
+        """!Sets reference to nviz toolwindow in layer manager"""
+        self.toolWin = toolWin
+        
+    def GetToolWin(self):
+        """!Returns reference to nviz toolwindow in layer manager"""
+        return self.toolWin
             
     def OnClose(self, event):
+        self.StopTimer(self.timerAnim)
         self.StopTimer(self.timerFly)
         # cleanup when window actually closes (on quit) and not just is hidden
         self.UnloadDataLayers(force = True)
@@ -298,14 +316,15 @@
             self.SetCurrent()
             self._display.ResizeWindow(size.width,
                                        size.height)
+        
+            # reposition checkbox in statusbar
+            self.parent.StatusbarReposition()
+            
+            # update statusbar
+            self.parent.StatusbarUpdate()
+            
         self.size = size
         
-        # reposition checkbox in statusbar
-        self.parent.StatusbarReposition()
-        
-        # update statusbar
-        self.parent.StatusbarUpdate()
-        
         event.Skip()
        
     def OnPaint(self, event):
@@ -334,6 +353,7 @@
                 self.lmgr.nviz.UpdatePage('light')
                 self.lmgr.nviz.UpdatePage('cplane')
                 self.lmgr.nviz.UpdatePage('decoration')
+                self.lmgr.nviz.UpdatePage('animation')
                 layer = self.GetSelectedLayer()
                 if layer:
                     if layer.type ==  'raster':
@@ -492,6 +512,12 @@
                 return texture.id
         return -1
         
+    def OnTimerAnim(self, event):
+         self.animation.Update()
+         
+    def GetAnimation(self):
+         return self.animation
+         
     def OnKeyDown(self, event):
         """!Key was pressed.
         
@@ -752,6 +778,12 @@
             if self.fly['mouseControl']:
                 self.StopTimer(self.timerFly)
                 self.fly['mouseControl'] = None
+                #for key in self.iview['dir'].keys():
+                    #self.iview[''][key] = -1
+                # this causes sudden change, but it should be there
+                #if hasattr(self.lmgr, "nviz"):
+                    #self.lmgr.nviz.UpdateSettings()
+                    
                 self.render['quick'] = False
                 self.Refresh(False)
             
@@ -788,8 +820,7 @@
             focus['z'] -= dz
             
             #update properties
-            evt = wxUpdateView(zExag = False)
-            wx.PostEvent(self, evt)
+            self.PostViewEvent()
             
             self.mouse['tmp'] = event.GetPositionTuple()
             self.render['quick'] = True
@@ -928,8 +959,7 @@
         focus['x'], focus['y'] = e, n
         self.saveHistory = True
         #update properties
-        evt = wxUpdateView(zExag = False)
-        wx.PostEvent(self, evt)
+        self.PostViewEvent()
         
         self.render['quick'] = False
         self.Refresh(False)
@@ -982,37 +1012,62 @@
             self.log.WriteLog(_("No point on surface"))
             self.log.WriteCmdLog('-' * 80)
     
+    def PostViewEvent(self, zExag = False):
+        """!Change view settings"""
+        event = wxUpdateView(zExag = zExag)
+        wx.PostEvent(self, event)
+        
     def OnQueryVector(self, event):
         """!Query vector on given position"""
         self.parent.QueryVector(*event.GetPosition())
+
+    def ChangeInnerView(self):
+        """!Get current viewdir and viewpoint and set view"""
+        view = self.view
+        iview = self.iview
+        (view['position']['x'], view['position']['y'],
+        iview['height']['value']) = self._display.GetViewpointPosition()
+        for key, val in zip(('x', 'y', 'z'), self._display.GetViewdir()):
+            iview['dir'][key] = val
         
-    def UpdateView(self, event):
+        iview['dir']['use'] = True
+        
+    def OnUpdateView(self, event):
         """!Change view settings"""
-        data = self.view
-        
-        ## multiple z-exag value from slider by original value computed in ogsf so it scales
-        ## correctly with maps of different height ranges        
-        if event and event.zExag and 'value' in data['z-exag']:
-            self._display.SetZExag(self.iview['z-exag']['original'] * data['z-exag']['value'])
+        if event:
+                self.UpdateView(zexag = event.zExag)
+                
+        self.saveHistory = True
+        if event:
+            event.Skip()
             
-        self._display.SetView(data['position']['x'], data['position']['y'],
-                              self.iview['height']['value'],
-                              data['persp']['value'],
-                              data['twist']['value'])
+            
+    def UpdateView(self, zexag = False):
+        """!Change view settings"""
+        view = self.view
+        iview = self.iview
+        if zexag and 'value' in view['z-exag']:
+            self._display.SetZExag(self.iview['z-exag']['original'] * view['z-exag']['value'])
         
-        if self.iview['focus']['x'] != -1:
+        
+        self._display.SetView(view['position']['x'], view['position']['y'],
+                              iview['height']['value'],
+                              view['persp']['value'],
+                              view['twist']['value'])
+        
+        if iview['dir']['use']:
+            self._display.SetViewdir(iview['dir']['x'], iview['dir']['y'], iview['dir']['z'])
+        
+        elif iview['focus']['x'] != -1:
             self._display.SetFocus(self.iview['focus']['x'], self.iview['focus']['y'],
                                    self.iview['focus']['z'])
-        if 'rotation' in self.iview:
-            if self.iview['rotation']:
-                self._display.SetRotationMatrix(self.iview['rotation'])
+                                       
+        if 'rotation' in iview:
+            if iview['rotation']:
+                self._display.SetRotationMatrix(iview['rotation'])
             else:
                 self._display.ResetRotation()
         
-        self.saveHistory = True
-        if event:
-            event.Skip()
-
     def UpdateLight(self, event):
         """!Change light settings"""
         data = self.light
@@ -1694,8 +1749,7 @@
         focus = self.iview['focus']
         focus['x'], focus['y'], focus['z'] = self._display.GetFocus()
         
-        event = wxUpdateView(zExag = False)
-        wx.PostEvent(self, event)
+        self.PostViewEvent()
         
     def UpdateMapObjProperties(self, event):
         """!Generic method to update data layer properties"""

Modified: grass/trunk/gui/wxpython/gui_modules/nviz_tools.py
===================================================================
--- grass/trunk/gui/wxpython/gui_modules/nviz_tools.py	2011-11-16 15:58:34 UTC (rev 49273)
+++ grass/trunk/gui/wxpython/gui_modules/nviz_tools.py	2011-11-16 16:02:14 UTC (rev 49274)
@@ -4,6 +4,11 @@
 @brief Nviz (3D view) tools window
 
 Classes:
+ - ScrolledPanel
+ - NTCValidator
+ - NumTextCtrl
+ - FloatSlider
+ - SymbolButton
  - NvizToolWindow
  - PositionWindow
  - ViewPositionWindow
@@ -27,9 +32,16 @@
 import string
 
 import wx
-import wx.lib.colourselect as csel
+import wx.lib.colourselect  as csel
 import wx.lib.scrolledpanel as SP
+import wx.lib.filebrowsebutton as filebrowse
+
 try:
+    from wx.lib.buttons import ThemedGenBitmapTextButton as BitmapTextButton
+except ImportError: # not sure about TGBTButton version
+    from wx.lib.buttons import GenBitmapTextButton as BitmapTextButton
+    
+try:
     import wx.lib.agw.flatnotebook as FN
 except ImportError:
     import wx.lib.flatnotebook as FN
@@ -50,12 +62,14 @@
 from preferences import globalSettings as UserSettings
 from gselect import VectorDBInfo
 
+from nviz_animation import EVT_ANIM_FIN, EVT_ANIM_UPDATE_IDX
 try:
     from nviz_mapdisp import wxUpdateView, wxUpdateLight, wxUpdateProperties,\
                             wxUpdateCPlane
     import wxnviz
 except ImportError:
     pass
+
 from debug import Debug
 
 
@@ -154,11 +168,67 @@
         Debug.msg(4, "FloatSlider.GetValue(): value = %f" % (val/self.coef))
         return val/self.coef
         
+        
+class SymbolButton(BitmapTextButton):
+    """!Button with symbol and label."""
+    def __init__(self, parent, usage, label, **kwargs):
+        """!Constructor
+        
+        @param parent parent (usually wx.Panel)
+        @param usage determines usage and picture
+        @param label displayed label
+        """
+        BitmapTextButton.__init__(self, parent = parent, label = " " + label, **kwargs)
+        
+        size = (15, 15)
+        buffer = wx.EmptyBitmap(*size)
+        dc = wx.MemoryDC()
+        dc.SelectObject(buffer)
+        maskColor = wx.Color(255, 255, 255)
+        dc.SetBrush(wx.Brush(maskColor))
+        dc.Clear()
+        
+        if usage == 'record':
+            self.DrawRecord(dc, size)
+        elif usage == 'stop':
+            self.DrawStop(dc, size)
+        elif usage == 'play':
+            self.DrawPlay(dc, size)
+        elif usage == 'pause':
+            self.DrawPause(dc, size)
+
+        buffer.SetMaskColour(maskColor)
+        self.SetBitmapLabel(buffer)
+        dc.SelectObject(wx.NullBitmap)
+        
+    def DrawRecord(self, dc, size):
+        """!Draw record symbol"""
+        dc.SetBrush(wx.Brush(wx.Color(255, 0, 0)))
+        dc.DrawCircle(size[0]/2, size[1] / 2, size[0] / 2)
+        
+    def DrawStop(self, dc, size):
+        """!Draw stop symbol"""
+        dc.SetBrush(wx.Brush(wx.Color(50, 50, 50)))
+        dc.DrawRectangle(0, 0, size[0], size[1])
+        
+    def DrawPlay(self, dc, size):
+        """!Draw play symbol"""
+        dc.SetBrush(wx.Brush(wx.Color(0, 255, 0)))
+        points = (wx.Point(0, 0), wx.Point(0, size[1]), wx.Point(size[0], size[1] / 2))
+        dc.DrawPolygon(points)
+        
+    def DrawPause(self, dc, size):
+        """!Draw pause symbol"""
+        dc.SetBrush(wx.Brush(wx.Color(50, 50, 50)))
+        dc.DrawRectangle(0, 0, 2 * size[0] / 5, size[1])
+        dc.DrawRectangle(3 * size[0] / 5, 0, 2 * size[0] / 5, size[1])
+        
+        
 class NvizToolWindow(FN.FlatNotebook):
     """!Nviz (3D view) tools panel
     """
     def __init__(self, parent, display, id = wx.ID_ANY,
-                 style = globalvar.FNPageStyle|FN.FNB_NO_X_BUTTON|FN.FNB_NO_NAV_BUTTONS,
+                 style = globalvar.FNPageStyle|FN.FNB_NO_X_BUTTON,
                  **kwargs):
         Debug.msg(5, "NvizToolWindow.__init__()")
         self.parent     = parent # GMFrame
@@ -191,8 +261,14 @@
         # analysis page
         self.AddPage(page = self._createAnalysisPage(),
                      text = " %s " % _("Analysis"))
+        # view page
+        self.AddPage(page = self._createAnimationPage(),
+                     text = " %s " % _("Animation"))
         
         self.UpdateSettings()
+        
+        self.mapWindow.SetToolWin(self)
+        
         self.pageChanging = False
         self.vetoGSelectEvt = False #when setting map, event is invoked
         self.mapWindow.render['quick'] = False
@@ -202,6 +278,9 @@
         self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnPageChanged)
         self.Bind(wx.EVT_SIZE, self.OnSize)
         
+        self.Bind(EVT_ANIM_FIN, self.OnAnimationFinished)
+        self.Bind(EVT_ANIM_UPDATE_IDX, self.OnAnimationUpdateIndex)
+        
         Debug.msg(3, "NvizToolWindow.__init__()")
         
         self.Update()
@@ -444,7 +523,154 @@
         panel.SetSizer(pageSizer)
         
         return panel
-
+        
+    def _createAnimationPage(self):
+        """!Create view settings page"""
+        panel = SP.ScrolledPanel(parent = self, id = wx.ID_ANY)
+        panel.SetupScrolling(scroll_x = False)
+        self.page['animation'] = { 'id' : 0,
+                                   'notebook' : self.GetId()}
+        
+        pageSizer = wx.BoxSizer(wx.VERTICAL)
+        box = wx.StaticBox (parent = panel, id = wx.ID_ANY,
+                            label = " %s " % (_("Animation")))
+        boxSizer = wx.StaticBoxSizer(box, wx.VERTICAL)
+        hSizer = wx.BoxSizer(wx.HORIZONTAL)
+        
+        self.win['anim'] = {}
+        # animation help text
+        help = wx.StaticText(parent = panel, id = wx.ID_ANY,
+                             label = _("Press 'Record' button and start changing the view. "
+                                       "It is recommended to use fly-through mode "
+                                       "(Map Display toolbar) to achieve smooth motion."))
+        self.win['anim']['help'] = help.GetId()
+        hSizer.Add(item = help, proportion = 0)
+        boxSizer.Add(item = hSizer, proportion = 1,
+                     flag = wx.ALL | wx.EXPAND, border = 5)
+                     
+        # animation controls
+        hSizer = wx.BoxSizer(wx.HORIZONTAL)
+        record = SymbolButton(parent = panel, id = wx.ID_ANY,
+                                    usage = "record", label = _("Record"))
+        play = SymbolButton(parent = panel, id = wx.ID_ANY,
+                                  usage = "play", label = _("Play"))
+        pause = SymbolButton(parent = panel, id = wx.ID_ANY,
+                                   usage = "pause", label = _("Pause"))
+        stop = SymbolButton(parent = panel, id = wx.ID_ANY,
+                                  usage = "stop", label = _("Stop"))
+        
+        self.win['anim']['record'] = record.GetId()
+        self.win['anim']['play'] = play.GetId()
+        self.win['anim']['pause'] = pause.GetId()
+        self.win['anim']['stop'] = stop.GetId()
+                            
+        self._createControl(panel, data = self.win['anim'], name = 'frameIndex',
+                            range = (0, 1), floatSlider = False,
+                            bind = (self.OnFrameIndex, None, self.OnFrameIndexText))
+        frameSlider = self.FindWindowById(self.win['anim']['frameIndex']['slider'])
+        frameText = self.FindWindowById(self.win['anim']['frameIndex']['text'])
+        infoLabel = wx.StaticText(parent = panel, id = wx.ID_ANY, label = _("Total number of frames :"))
+        info = wx.StaticText(parent = panel, id = wx.ID_ANY)
+        self.win['anim']['info'] = info.GetId()
+        
+        fpsLabel = wx.StaticText(parent = panel, id = wx.ID_ANY, label = _("Frame rate (FPS):"))
+        fps = wx.SpinCtrl(parent = panel, id = wx.ID_ANY, size = (65, -1),
+                           initial = UserSettings.Get(group = 'nviz', key = 'animation', subkey = 'fps'),
+                           min = 1,
+                           max = 50)
+        self.win['anim']['fps'] = fps.GetId()
+        fps.SetToolTipString(_("Frames are recorded with given frequency (FPS). "))
+                            
+        record.Bind(wx.EVT_BUTTON, self.OnRecord)
+        play.Bind(wx.EVT_BUTTON, self.OnPlay)
+        stop.Bind(wx.EVT_BUTTON, self.OnStop)
+        pause.Bind(wx.EVT_BUTTON, self.OnPause)
+        fps.Bind(wx.EVT_SPINCTRL, self.OnFPS)
+        
+        hSizer.Add(item = record, proportion = 0)
+        hSizer.Add(item = play, proportion = 0)
+        hSizer.Add(item = pause, proportion = 0)
+        hSizer.Add(item = stop, proportion = 0)
+        boxSizer.Add(item = hSizer, proportion = 0,
+                     flag = wx.ALL | wx.EXPAND, border = 3)
+        
+        sliderBox = wx.BoxSizer(wx.HORIZONTAL)
+        sliderBox.Add(item = frameSlider, proportion = 1, border = 5, flag = wx.EXPAND | wx.RIGHT)
+        sliderBox.Add(item = frameText, proportion = 0, border = 5, flag = wx.EXPAND| wx.RIGHT | wx.LEFT)
+        boxSizer.Add(item = sliderBox, proportion = 0, flag = wx.EXPAND)
+        
+        # total number of frames
+        hSizer = wx.BoxSizer(wx.HORIZONTAL)
+        hSizer.Add(item = infoLabel, proportion = 0, flag = wx.RIGHT, border = 5)
+        hSizer.Add(item = info, proportion = 0, flag = wx.LEFT, border = 5)
+        
+        boxSizer.Add(item = hSizer, proportion = 0,
+                     flag = wx.ALL | wx.EXPAND, border = 5)
+                     
+        # frames per second
+        hSizer = wx.BoxSizer(wx.HORIZONTAL)
+        hSizer.Add(item = fpsLabel, proportion = 0, flag = wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, border = 5)
+        hSizer.Add(item = fps, proportion = 0, flag = wx.LEFT | wx.ALIGN_CENTER_VERTICAL, border = 5)
+        
+        boxSizer.Add(item = hSizer, proportion = 0,
+                     flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 5)
+        pageSizer.Add(item = boxSizer, proportion = 0,
+                      flag = wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM,
+                      border = 3)
+                      
+        # save animation
+        self.win['anim']['save'] = {}
+        self.win['anim']['save']['image'] = {}
+        box = wx.StaticBox (parent = panel, id = wx.ID_ANY,
+                            label = " %s " % (_("Save image sequence")))
+        boxSizer = wx.StaticBoxSizer(box, wx.VERTICAL)
+        vSizer = wx.BoxSizer(wx.VERTICAL)
+        gridSizer = wx.GridBagSizer(vgap = 5, hgap = 10)
+        
+        pwd = os.getcwd()
+        dir = filebrowse.DirBrowseButton(parent = panel, id = wx.ID_ANY,
+                                         labelText = _("Choose a directory:"),
+                                         dialogTitle = _("Choose a directory for images"),
+                                         buttonText = _('Browse'),
+                                         startDirectory = pwd)
+        dir.SetValue(pwd)
+        prefixLabel = wx.StaticText(parent = panel, id = wx.ID_ANY, label = _("File prefix:"))
+        prefixCtrl = wx.TextCtrl(parent = panel, id = wx.ID_ANY, size = (100, -1),
+                                 value = UserSettings.Get(group = 'nviz',
+                                                          key = 'animation', subkey = 'prefix'))
+        prefixCtrl.SetToolTipString(_("Generated files names will look like this: prefix_1.ppm, prefix_2.ppm, ..."))
+        fileTypeLabel = wx.StaticText(parent = panel, id = wx.ID_ANY, label = _("File format:"))
+        fileTypeCtrl = wx.Choice(parent = panel, id = wx.ID_ANY, choices = ["PPM", "TIF"])
+        
+        save = wx.Button(parent = panel, id = wx.ID_ANY,
+                         label = "Save")
+                         
+        self.win['anim']['save']['image']['dir'] = dir.GetId()
+        self.win['anim']['save']['image']['prefix'] = prefixCtrl.GetId()
+        self.win['anim']['save']['image']['format'] = fileTypeCtrl.GetId()
+        self.win['anim']['save']['image']['confirm'] = save.GetId()
+        
+        boxSizer.Add(item = dir, proportion = 0, flag = wx.ALL | wx.EXPAND, border = 3)
+        
+        gridSizer.Add(item = prefixLabel, pos = (0, 0), flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
+        gridSizer.Add(item = prefixCtrl, pos = (0, 1), flag = wx.EXPAND )
+        gridSizer.Add(item = fileTypeLabel, pos = (1, 0), flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
+        gridSizer.Add(item = fileTypeCtrl, pos = (1, 1), flag = wx.EXPAND )
+        
+        boxSizer.Add(item = gridSizer, proportion = 1,
+                     flag = wx.ALL | wx.EXPAND, border = 5)
+        boxSizer.Add(item = save, proportion = 0, flag = wx.ALL | wx.ALIGN_RIGHT, border = 5)
+        
+        save.Bind(wx.EVT_BUTTON, self.OnSaveAnimation)
+        
+        pageSizer.Add(item = boxSizer, proportion = 0,
+                      flag = wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 
+                      border = 3)
+        
+        panel.SetSizer(pageSizer)
+        
+        return panel
+        
     def _createDataPage(self):
         """!Create data (surface, vector, volume) settings page"""
 
@@ -1944,7 +2170,195 @@
             return self.mapWindow.GetLayerByName(name, mapType = '3d-raster', dataType = 'nviz')
         
         return None
-    
+        
+    def OnRecord(self, event):
+        """!Animation: start recording"""
+        anim = self.mapWindow.GetAnimation()
+        if not anim.IsPaused():
+            if anim.Exists() and not anim.IsSaved():
+                msg = _("Do you want to record new animation without saving the previous one?")
+                dlg = wx.MessageDialog(parent = self,
+                                       message = msg,
+                                       caption =_("Animation already axists"),
+                                       style = wx.YES_NO | wx.CENTRE)
+                if dlg.ShowModal() == wx.ID_NO:
+                    dlg.Destroy()
+                    return
+                
+        
+            anim.Clear()
+            self.UpdateFrameIndex(0)
+            self.UpdateFrameCount()
+            
+        anim.SetPause(False)
+        anim.SetMode(mode = 'record')
+        anim.Start()
+        
+        self.FindWindowById(self.win['anim']['play']).Disable()
+        self.FindWindowById(self.win['anim']['record']).Disable()
+        self.FindWindowById(self.win['anim']['pause']).Enable()
+        self.FindWindowById(self.win['anim']['stop']).Enable()
+        self.FindWindowById(self.win['anim']['frameIndex']['slider']).Disable()
+        self.FindWindowById(self.win['anim']['frameIndex']['text']).Disable()
+        
+    def OnPlay(self, event):
+        """!Animation: replay"""
+        anim = self.mapWindow.GetAnimation()
+        anim.SetPause(False)
+        anim.SetMode(mode = 'play')
+        anim.Start()
+        
+        self.FindWindowById(self.win['anim']['play']).Disable()
+        self.FindWindowById(self.win['anim']['record']).Disable()
+        self.FindWindowById(self.win['anim']['pause']).Enable()
+        self.FindWindowById(self.win['anim']['stop']).Enable()
+        self.FindWindowById(self.win['anim']['frameIndex']['slider']).Enable()
+        self.FindWindowById(self.win['anim']['frameIndex']['text']).Enable()
+        
+    def OnStop(self, event):
+        """!Animation: stop recording/replaying"""
+        anim = self.mapWindow.GetAnimation()
+        anim.SetPause(False)
+        if anim.GetMode() == 'save':
+            anim.StopSaving()
+        if anim.IsRunning():
+            anim.Stop()
+        
+        self.UpdateFrameIndex(0)
+        
+        self.FindWindowById(self.win['anim']['play']).Enable()
+        self.FindWindowById(self.win['anim']['record']).Enable()
+        self.FindWindowById(self.win['anim']['pause']).Disable()
+        self.FindWindowById(self.win['anim']['stop']).Disable()
+        self.FindWindowById(self.win['anim']['frameIndex']['slider']).Disable()
+        self.FindWindowById(self.win['anim']['frameIndex']['text']).Disable()
+        
+    def OnPause(self, event):
+        """!Pause animation"""
+        anim = self.mapWindow.GetAnimation()
+        
+        anim.SetPause(True)
+        mode = anim.GetMode()
+        if anim.IsRunning():
+            anim.Pause()
+            
+        if mode == "record":
+            self.FindWindowById(self.win['anim']['play']).Disable()
+            self.FindWindowById(self.win['anim']['record']).Enable()
+            self.FindWindowById(self.win['anim']['frameIndex']['slider']).Disable()
+            self.FindWindowById(self.win['anim']['frameIndex']['text']).Disable()
+        elif mode == 'play':
+            self.FindWindowById(self.win['anim']['record']).Disable()
+            self.FindWindowById(self.win['anim']['play']).Enable()
+            self.FindWindowById(self.win['anim']['frameIndex']['slider']).Enable()
+            self.FindWindowById(self.win['anim']['frameIndex']['text']).Enable()
+        
+        self.FindWindowById(self.win['anim']['pause']).Disable()
+        self.FindWindowById(self.win['anim']['stop']).Enable()
+
+        
+    def OnFrameIndex(self, event):
+        """!Frame index changed (by slider)"""
+        index = event.GetInt()
+        self.UpdateFrameIndex(index = index, sliderWidget = False)
+        
+    def OnFrameIndexText(self, event):
+        """!Frame index changed by (textCtrl)"""
+        index = event.GetValue()
+        self.UpdateFrameIndex(index = index, textWidget = False)
+        
+    def OnFPS(self, event):
+        """!Frames per second changed"""
+        anim = self.mapWindow.GetAnimation()
+        anim.SetFPS(event.GetInt())
+        
+    def UpdateFrameIndex(self, index, sliderWidget = True, textWidget = True, goToFrame = True):
+        """!Update frame index"""
+        anim = self.mapWindow.GetAnimation()
+        
+        # check index
+        frameCount = anim.GetFrameCount()
+        if index >= frameCount:
+            index = frameCount - 1
+        if index < 0:
+            index = 0
+            
+        if sliderWidget:
+            slider = self.FindWindowById(self.win['anim']['frameIndex']['slider'])
+            slider.SetValue(index)
+        if textWidget:
+            text = self.FindWindowById(self.win['anim']['frameIndex']['text'])
+            text.SetValue(int(index))
+        
+        # if called from tool window, update frame
+        if goToFrame:
+            anim.GoToFrame(int(index))
+            
+    def UpdateFrameCount(self):
+        """!Update frame count label"""
+        anim = self.mapWindow.GetAnimation()
+        count = anim.GetFrameCount()
+        self.FindWindowById(self.win['anim']['info']).SetLabel(str(count))
+        
+    def OnAnimationFinished(self, event):
+        """!Animation finished"""
+        anim = self.mapWindow.GetAnimation()
+        self.UpdateFrameIndex(index = 0)
+        
+        slider = self.FindWindowById(self.win['anim']['frameIndex']['slider'])
+        text = self.FindWindowById(self.win['anim']['frameIndex']['text'])
+        
+        if event.mode == 'record':
+            count = anim.GetFrameCount()
+            slider.SetMax(count)
+            self.UpdateFrameCount()
+            
+        self.FindWindowById(self.win['anim']['pause']).Disable()
+        self.FindWindowById(self.win['anim']['stop']).Disable()
+        self.FindWindowById(self.win['anim']['record']).Enable()
+        self.FindWindowById(self.win['anim']['play']).Enable()
+        self.FindWindowById(self.win['anim']['frameIndex']['slider']).Disable()
+        self.FindWindowById(self.win['anim']['frameIndex']['text']).Disable()
+        self.FindWindowById(self.win['anim']['save']['image']['confirm']).Enable()
+        
+        self.mapWindow.render['quick'] = False
+        self.mapWindow.Refresh(False)
+        
+    def OnAnimationUpdateIndex(self, event):
+        """!Animation: frame index changed"""
+        if event.mode == 'record':
+            self.UpdateFrameCount()
+        elif event.mode == 'play':
+            self.UpdateFrameIndex(index = event.index, goToFrame = False)
+        
+    def OnSaveAnimation(self, event):
+        """!Save animation as a sequence of images"""
+        anim = self.mapWindow.GetAnimation()
+        
+        prefix = self.FindWindowById(self.win['anim']['save']['image']['prefix']).GetValue()
+        format = self.FindWindowById(self.win['anim']['save']['image']['format']).GetSelection()
+        dir = self.FindWindowById(self.win['anim']['save']['image']['dir']).GetValue()
+        
+        if not prefix:
+            gcmd.GMessage(parent = self,
+                          message = _("No file prefix given."))
+            return
+        elif not os.path.exists(dir):
+            gcmd.GMessage(parent = self,
+                          message = _("Directory %s does not exist.") % dir)
+            return
+            
+        self.FindWindowById(self.win['anim']['pause']).Disable()
+        self.FindWindowById(self.win['anim']['stop']).Enable()
+        self.FindWindowById(self.win['anim']['record']).Disable()
+        self.FindWindowById(self.win['anim']['play']).Disable()
+        self.FindWindowById(self.win['anim']['frameIndex']['slider']).Disable()
+        self.FindWindowById(self.win['anim']['frameIndex']['text']).Disable()
+        
+        self.FindWindowById(self.win['anim']['save']['image']['confirm']).Disable()
+        
+        anim.SaveAnimationFile(path = dir, prefix = prefix, format = format)
+        
     def OnNewConstant(self, event):
         """!Create new surface with constant value"""
         #TODO settings
@@ -2336,6 +2750,8 @@
         sizer.Add(item = sw, pos = (2, 0), flag = wx.ALIGN_CENTER)
         sizer.Add(item = w, pos = (1, 0), flag = wx.ALIGN_CENTER)
         
+        
+        
     def __GetWindowName(self, data, id):
         for name in data.iterkeys():
             if type(data[name]) is type({}):
@@ -2513,7 +2929,7 @@
         for win in self.win['view'][winName].itervalues():
             self.FindWindowById(win).SetValue(value)
 
-                
+        self.mapWindow.iview['dir']['use'] = False
         self.mapWindow.render['quick'] = True
         if self.mapDisplay.IsAutoRendered():
             self.mapWindow.Refresh(False)
@@ -4191,10 +4607,33 @@
             self.FindWindowById(self.win['cplane']['position']['z']['text']).SetValue(zRange[0])
             self.OnCPlaneSelection(None)
             
+        elif pageId == 'animation':
+            self.UpdateAnimationPage()
             
         self.Update()
         self.pageChanging = False
         
+    def UpdateAnimationPage(self):
+        """!Update animation page"""
+        # wrap help text according to tool window
+        help = self.FindWindowById(self.win['anim']['help'])
+        width = help.GetGrandParent().GetSizeTuple()[0]
+        help.Wrap(width - 15)
+        anim = self.mapWindow.GetAnimation()
+        if anim.Exists():
+            self.FindWindowById(self.win['anim']['play']).Enable()
+        else:
+            self.UpdateFrameIndex(index = 0)
+            
+        self.UpdateFrameCount()
+        
+        self.FindWindowById(self.win['anim']['play']).Disable()
+        self.FindWindowById(self.win['anim']['record']).Enable()
+        self.FindWindowById(self.win['anim']['pause']).Disable()
+        self.FindWindowById(self.win['anim']['stop']).Disable()
+        self.FindWindowById(self.win['anim']['frameIndex']['slider']).Disable()
+        self.FindWindowById(self.win['anim']['frameIndex']['text']).Disable()
+        
     def UpdateCPlanePage(self, index):
         """!Update widgets according to selected clip plane"""
         if index == -1:   
@@ -4209,14 +4648,7 @@
                 
     def UpdateSurfacePage(self, layer, data, updateName = True):
         """!Update surface page"""
-        ret = gcmd.RunCommand('r.info',
-                              read = True,
-                              flags = 'm',
-                              map = layer.name)
-        if ret:
-            desc = ret.split('=')[1].rstrip('\n')
-        else:
-            desc = None
+        desc = grass.raster_info(layer.name)['title']
         if updateName:
             self.FindWindowById(self.win['surface']['map']).SetValue(layer.name)
         self.FindWindowById(self.win['surface']['desc']).SetLabel(desc)
@@ -4730,6 +5162,7 @@
         return x, y
         
     def OnMouse(self, event):
+        self.mapWindow.iview['dir']['use'] = False # use focus instead of viewdir
         PositionWindow.OnMouse(self, event)
         if event.LeftIsDown():
             self.mapWindow.render['quick'] = self.quick

Modified: grass/trunk/gui/wxpython/gui_modules/preferences.py
===================================================================
--- grass/trunk/gui/wxpython/gui_modules/preferences.py	2011-11-16 15:58:34 UTC (rev 49273)
+++ grass/trunk/gui/wxpython/gui_modules/preferences.py	2011-11-16 16:02:14 UTC (rev 49274)
@@ -549,6 +549,10 @@
                         'turn' : 1,
                         }
                     },
+                'animation' : {
+                    'fps' : 24,
+                    'prefix' : _("animation")
+                    },
                 'surface' : {
                     'shine': {
                         'map' : False,
@@ -781,6 +785,12 @@
         self.internalSettings['nviz']['view']['focus']['x'] = -1
         self.internalSettings['nviz']['view']['focus']['y'] = -1
         self.internalSettings['nviz']['view']['focus']['z'] = -1
+        self.internalSettings['nviz']['view']['dir'] = {}
+        self.internalSettings['nviz']['view']['dir']['x'] = -1
+        self.internalSettings['nviz']['view']['dir']['y'] = -1
+        self.internalSettings['nviz']['view']['dir']['z'] = -1
+        self.internalSettings['nviz']['view']['dir']['use'] = False
+        
         for decor in ('arrow', 'scalebar'):
             self.internalSettings['nviz'][decor] = {}
             self.internalSettings['nviz'][decor]['position'] = {}

Modified: grass/trunk/gui/wxpython/gui_modules/workspace.py
===================================================================
--- grass/trunk/gui/wxpython/gui_modules/workspace.py	2011-11-16 15:58:34 UTC (rev 49273)
+++ grass/trunk/gui/wxpython/gui_modules/workspace.py	2011-11-16 16:02:14 UTC (rev 49274)
@@ -514,6 +514,19 @@
         iview['focus']['x'] = self.__processLayerNvizNode(node_focus, 'x', int)
         iview['focus']['y'] = self.__processLayerNvizNode(node_focus, 'y', int)
         iview['focus']['z'] = self.__processLayerNvizNode(node_focus, 'z', int)
+        node_dir = node_view.find('dir')
+        if node_dir:
+            iview['dir'] = {}
+            iview['dir']['x'] = self.__processLayerNvizNode(node_dir, 'x', int)
+            iview['dir']['y'] = self.__processLayerNvizNode(node_dir, 'y', int)
+            iview['dir']['z'] = self.__processLayerNvizNode(node_dir, 'z', int)
+            iview['dir']['use'] = True
+        else:
+            iview['dir'] = {}
+            iview['dir']['x'] = -1
+            iview['dir']['y'] = -1
+            iview['dir']['z'] = -1
+            iview['dir']['use'] = False
         
         view['background'] = {}
         color = self.__processLayerNvizNode(node_view, 'background_color', str)

Modified: grass/trunk/gui/wxpython/gui_modules/wxnviz.py
===================================================================
--- grass/trunk/gui/wxpython/gui_modules/wxnviz.py	2011-11-16 15:58:34 UTC (rev 49273)
+++ grass/trunk/gui/wxpython/gui_modules/wxnviz.py	2011-11-16 16:02:14 UTC (rev 49274)
@@ -170,6 +170,15 @@
         Debug.msg(3, "Nviz::SetView(): x=%f, y=%f, height=%f, persp=%f, twist=%f",
                   x, y, height, persp, twist)
                 
+    def GetViewpointPosition(self):
+        x = c_double()
+        y = c_double()
+        h = c_double()
+        Nviz_get_viewpoint_height(byref(h))
+        Nviz_get_viewpoint_position(byref(x), byref(y))
+        
+        return (x.value, y.value, h.value)
+        
     def LookHere(self, x, y):
         """!Look here feature 
         @param x,y screen coordinates
@@ -200,6 +209,22 @@
         Debug.msg(3, "Nviz::SetFocus()")
         Nviz_set_focus(self.data, x, y, z)
         
+    def GetViewdir(self):
+        """!Get viewdir"""
+        Debug.msg(3, "Nviz::GetViewdir()")
+        dir = (c_float * 3)()
+        GS_get_viewdir(byref(dir))
+        
+        return dir[0], dir[1], dir[2]
+        
+    def SetViewdir(self, x, y, z):
+        """!Set viewdir"""
+        Debug.msg(3, "Nviz::SetViewdir(): x=%f, y=%f, z=%f" % (x, y, z))
+        dir = (c_float * 3)()
+        for i, coord in enumerate((x, y, z)):
+            dir[i] = coord
+        GS_set_viewdir(byref(dir))
+                
     def SetZExag(self, z_exag):
         """!Set z-exag value
         



More information about the grass-commit mailing list