#----------------------------------------------------------------------
# Name:        wxPython.lib.vtk
# Purpose:     Provides a wrapper around the vtkRenderWindow from the
#              VTK Visualization Toolkit.  Requires the VTK Python
#              extensions from http://www.kitware.com/
#
# Author:      Robin Dunn
#
# Created:     16-Nov-1999
# RCS-ID:      $Id: vtk.py,v 1.7 2001/07/06 17:31:06 RD Exp $
# Copyright:   (c) 1999 by Total Control Software
# Licence:     wxWindows license
#----------------------------------------------------------------------

# This class has been rewritten and improved by Prabhu Ramachandran
# <prabhu@aero.iitm.ernet.in>.  It has been tested under Win32 and
# Linux.  Many thanks to Eric Boix <frog@creatis.insa-lyon.fr> for
# testing it under Windows and finding and fixing many errors.
# Thanks also to Sebastien BARRE <sebastien@barre.nom.fr> for his
# suggestions.


try:
    from vtkpython import *
except ImportError:
    raise ImportError, "VTK extension module not found"

from wxPython.wx import *
import math

#----------------------------------------------------------------------

DEBUG = 0

def debug(msg):
    if DEBUG:
        print msg


class wxVTKRenderWindowBase(wxWindow):
    """
    A base class that enables one to embed a vtkRenderWindow into
    a wxPython widget.  This class embeds the RenderWindow correctly
    under different platforms.  Provided are some empty methods that
    can be overloaded to provide a user defined interaction behaviour.
    The event handling functions have names that are similar to the
    ones in the vtkInteractorStyle class included with VTK.
    """

    def __init__(self, parent, id, position=wxDefaultPosition,
                 size=wxDefaultSize, style=0):
        wxWindow.__init__(self, parent, id, position, size, style | wxWANTS_CHARS)
        self._RenderWindow = vtkRenderWindow()

        self.__InExpose = 0
        self.__Created = 0

        if wxPlatform != '__WXMSW__':
            # We can't get the handle in wxGTK until after the widget
            # is created, the window create event happens later so we'll
            # catch it there
            EVT_WINDOW_CREATE(self, self.OnCreateWindow)
            EVT_PAINT       (self, self.OnExpose)
        else:
            # but in MSW, the window create event happens durring the above
            # call to __init__ so we have to do it here.
            hdl = self.GetHandle()
            self._RenderWindow.SetWindowInfo(str(hdl))
            EVT_PAINT       (self, self.OnExpose)
            self.__Created = 1

        # common for all platforms
        EVT_SIZE        (self, self.OnConfigure)

        # setup the user defined events.
        self.SetupEvents()


    def SetupEvents(self):
        "Setup the user defined event bindings."
        # Remember to bind everything to self.box and NOT self
        EVT_LEFT_DOWN    (self, self.OnLeftButtonDown)
        EVT_MIDDLE_DOWN  (self, self.OnMiddleButtonDown)
        EVT_RIGHT_DOWN   (self, self.OnRightButtonDown)
        EVT_LEFT_UP      (self, self.OnLeftButtonUp)
        EVT_MIDDLE_UP    (self, self.OnMiddleButtonUp)
        EVT_RIGHT_UP     (self, self.OnRightButtonUp)
        EVT_MOTION       (self, self.OnMouseMove)
        EVT_ENTER_WINDOW (self, self.OnEnter)
        EVT_LEAVE_WINDOW (self, self.OnLeave)
        EVT_CHAR         (self, self.OnChar)
        # Add your bindings if you want them in the derived class.


    def GetRenderer(self):
        self._RenderWindow.GetRenderers().InitTraversal()
        return self._RenderWindow.GetRenderers().GetNextItem()


    def GetRenderWindow(self):
        return self._RenderWindow


    def Render(self):
        if self.__Created:
            # if block needed because calls to render before creation
            # will prevent the renderwindow from being embedded into a
            # wxPython widget.
            self._RenderWindow.Render()


    def OnExpose(self, event):
        # I need this for the MDIDemo.  Somehow OnCreateWindow was
        # not getting called.
        if not self.__Created:
            self.OnCreateWindow(event)
        if (not self.__InExpose):
            self.__InExpose = 1
            dc = wxPaintDC(self)
            self._RenderWindow.Render()
            self.__InExpose = 0


    def OnCreateWindow(self, event):
	hdl = self.GetHandle()
        try:
            self._RenderWindow.SetParentInfo(str(hdl))
        except:
            self._RenderWindow.SetWindowInfo(str(hdl))
            msg = "Warning:\n "\
                  "Unable to call vtkRenderWindow.SetParentInfo\n\n"\
                  "Using the SetWindowInfo method instead.  This\n"\
                  "is likely to cause a lot of flicker when\n"\
                  "rendering in the vtkRenderWindow.  Please\n"\
                  "use a recent Nightly VTK release (later than\n"\
                  "March 10 2001) to eliminate this problem."
            dlg = wxMessageDialog(NULL, msg, "Warning!",
                                   wxOK |wxICON_INFORMATION)
            dlg.ShowModal()
            dlg.Destroy()
        self.__Created = 1


    def OnConfigure(self, event):
        sz = self.GetSize()
        self.SetSize(sz)
        # Ugly hack that according to Eric Boix is necessary under
        # Windows.  If possible Try commenting this out and test under
        # Windows.
        #self._RenderWindow.GetSize()
        #
	self._RenderWindow.SetSize(sz.width, sz.height)


    def OnLeftButtonDown(self, event):
        "Left mouse button pressed."
        pass


    def OnMiddleButtonDown(self, event):
        "Middle mouse button pressed."
        pass


    def OnRightButtonDown(self, event):
        "Right mouse button pressed."
        pass


    def OnLeftButtonUp(self, event):
        "Left mouse button released."
        pass


    def OnMiddleButtonUp(self, event):
        "Middle mouse button released."
        pass


    def OnRightButtonUp(self, event):
        "Right mouse button released."
        pass


    def OnMouseMove(self, event):
        "Mouse has moved."
        pass


    def OnEnter(self, event):
        "Entering the vtkRenderWindow."
        pass


    def OnLeave(self, event):
        "Leaving the vtkRenderWindow."
        pass


    def OnChar(self, event):
        "Process Key events."
        pass


    def OnKeyDown(self, event):
        "Key pressed down."
        pass


    def OnKeyUp(self, event):
        "Key released."
        pass




class wxVTKRenderWindow(wxVTKRenderWindowBase):
    """
    An example of a fully functional wxVTKRenderWindow that is
    based on the vtkRenderWidget.py provided with the VTK sources.
    """

    def __init__(self, parent, id, position=wxDefaultPosition,
                 size=wxDefaultSize, style=0):
        wxVTKRenderWindowBase.__init__(self, parent, id, position, size,
                                       style)

        self._CurrentRenderer = None
        self._CurrentCamera = None
        self._CurrentZoom = 1.0
        self._CurrentLight = None

        self._ViewportCenterX = 0
        self._ViewportCenterY = 0

        self._Picker = vtkCellPicker()
        self._PickedAssembly = None
        self._PickedProperty = vtkProperty()
        self._PickedProperty.SetColor(1,0,0)
        self._PrePickedProperty = None

        self._OldFocus = None

        # these record the previous mouse position
        self._LastX = 0
        self._LastY = 0


    def OnLeftButtonDown(self, event):
        "Left mouse button pressed."
        self.StartMotion(event)


    def OnMiddleButtonDown(self, event):
        "Middle mouse button pressed."
        self.StartMotion(event)


    def OnRightButtonDown(self, event):
        "Right mouse button pressed."
        self.StartMotion(event)


    def OnLeftButtonUp(self, event):
        "Left mouse button released."
        self.EndMotion(event)


    def OnMiddleButtonUp(self, event):
        "Middle mouse button released."
        self.EndMotion(event)


    def OnRightButtonUp(self, event):
        "Right mouse button released."
        self.EndMotion(event)


    def OnMouseMove(self, event):
        event.x, event.y = event.GetPositionTuple()
        if event.LeftIsDown():
            if event.ShiftDown():
                self.Pan(event.x, event.y)
            else:
                self.Rotate(event.x, event.y)

        elif event.MiddleIsDown():
            self.Pan(event.x, event.y)

        elif event.RightIsDown():
            self.Zoom(event.x, event.y)


    def OnEnter(self, event):
        self.__OldFocus = wxWindow_FindFocus()
        self.SetFocus()
        x, y = event.GetPositionTuple()
        self.UpdateRenderer(x,y)


    def OnLeave(self, event):
        if (self._OldFocus != None):
            self.__OldFocus.SetFocus()


    def OnChar(self, event):
        key = event.KeyCode()
        if (key == ord('r')) or (key == ord('R')):
            self.Reset()
        elif (key == ord('w')) or (key == ord('W')):
            self.Wireframe()
        elif (key == ord('s')) or (key == ord('S')):
            self.Surface()
        elif (key == ord('p')) or (key == ord('P')):
            x, y = event.GetPositionTuple()
            self.PickActor(x, y)
        else:
            event.Skip()


    # Start of internal functions
    def GetZoomFactor(self):
        return self._CurrentZoom


    def SetZoomFactor(self, zf):
        self._CurrentZoom = zf


    def GetPicker(self):
        return self._Picker


    def Render(self):
        if (self._CurrentLight):
            light = self._CurrentLight
            light.SetPosition(self._CurrentCamera.GetPosition())
            light.SetFocalPoint(self._CurrentCamera.GetFocalPoint())

        wxVTKRenderWindowBase.Render(self)


    def UpdateRenderer(self, x, y):
        """
        UpdateRenderer will identify the renderer under the mouse and set
        up _CurrentRenderer, _CurrentCamera, and _CurrentLight.
        """
        sz = self.GetSize()
        windowX = sz.width
        windowY = sz.height

        renderers = self._RenderWindow.GetRenderers()
        numRenderers = renderers.GetNumberOfItems()

        self._CurrentRenderer = None
        renderers.InitTraversal()
        for i in range(0,numRenderers):
            renderer = renderers.GetNextItem()
            vx,vy = (0,0)
            if (windowX > 1):
                vx = float (x)/(windowX-1)
            if (windowY > 1):
                vy = (windowY-float(y)-1)/(windowY-1)
            (vpxmin,vpymin,vpxmax,vpymax) = renderer.GetViewport()

            if (vx >= vpxmin and vx <= vpxmax and
                vy >= vpymin and vy <= vpymax):
                self._CurrentRenderer = renderer
                self._ViewportCenterX = float(windowX)*(vpxmax-vpxmin)/2.0\
                                        +vpxmin
                self._ViewportCenterY = float(windowY)*(vpymax-vpymin)/2.0\
                                        +vpymin
                self._CurrentCamera = self._CurrentRenderer.GetActiveCamera()
                lights = self._CurrentRenderer.GetLights()
                lights.InitTraversal()
                self._CurrentLight = lights.GetNextItem()
                break

        self._LastX = x
        self._LastY = y


    def GetCurrentRenderer(self):
        return self._CurrentRenderer


    def StartMotion(self, event):
        x, y = event.GetPositionTuple()
        self.UpdateRenderer(x,y)
        self.CaptureMouse()


    def EndMotion(self, event=None):
        if self._CurrentRenderer:
            self.Render()
        self.ReleaseMouse()


    def Rotate(self,x,y):
        if self._CurrentRenderer:

            self._CurrentCamera.Azimuth(self._LastX - x)
            self._CurrentCamera.Elevation(y - self._LastY)
            self._CurrentCamera.OrthogonalizeViewUp()

            self._LastX = x
            self._LastY = y

            self._CurrentRenderer.ResetCameraClippingRange()
            self.Render()


    def PanAbsolute(self, x_vec, y_vec):
        if self._CurrentRenderer:

            renderer = self._CurrentRenderer
            camera = self._CurrentCamera
            (pPoint0,pPoint1,pPoint2) = camera.GetPosition()
            (fPoint0,fPoint1,fPoint2) = camera.GetFocalPoint()

            if (camera.GetParallelProjection()):
                renderer.SetWorldPoint(fPoint0,fPoint1,fPoint2,1.0)
                renderer.WorldToDisplay()
                fx,fy,fz = renderer.GetDisplayPoint()
                renderer.SetDisplayPoint(fx+x_vec,
                                         fy+y_vec,
                                         fz)
                renderer.DisplayToWorld()
                fx,fy,fz,fw = renderer.GetWorldPoint()
                camera.SetFocalPoint(fx,fy,fz)

                renderer.SetWorldPoint(pPoint0,pPoint1,pPoint2,1.0)
                renderer.WorldToDisplay()
                fx,fy,fz = renderer.GetDisplayPoint()
                renderer.SetDisplayPoint(fx+x_vec,
                                         fy+y_vec,
                                         fz)
                renderer.DisplayToWorld()
                fx,fy,fz,fw = renderer.GetWorldPoint()
                camera.SetPosition(fx,fy,fz)

            else:
                (fPoint0,fPoint1,fPoint2) = camera.GetFocalPoint()
                # Specify a point location in world coordinates
                renderer.SetWorldPoint(fPoint0,fPoint1,fPoint2,1.0)
                renderer.WorldToDisplay()
                # Convert world point coordinates to display coordinates
                dPoint = renderer.GetDisplayPoint()
                focalDepth = dPoint[2]

                aPoint0 = self._ViewportCenterX + x_vec
                aPoint1 = self._ViewportCenterY + y_vec

                renderer.SetDisplayPoint(aPoint0,aPoint1,focalDepth)
                renderer.DisplayToWorld()

                (rPoint0,rPoint1,rPoint2,rPoint3) = renderer.GetWorldPoint()
                if (rPoint3 != 0.0):
                    rPoint0 = rPoint0/rPoint3
                    rPoint1 = rPoint1/rPoint3
                    rPoint2 = rPoint2/rPoint3

                camera.SetFocalPoint((fPoint0 - rPoint0) + fPoint0,
                                     (fPoint1 - rPoint1) + fPoint1,
                                     (fPoint2 - rPoint2) + fPoint2)

                camera.SetPosition((fPoint0 - rPoint0) + pPoint0,
                                   (fPoint1 - rPoint1) + pPoint1,
                                   (fPoint2 - rPoint2) + pPoint2)

            self.Render()


    def Pan(self, x, y):
        self.PanAbsolute(x - self._LastX, - y + self._LastY)
        self._LastX = x
        self._LastY = y


    def Zoom(self,x,y):
        if self._CurrentRenderer:

            renderer = self._CurrentRenderer
            camera = self._CurrentCamera

            zoomFactor = math.pow(1.02,(0.5*(self._LastY - y)))
            self._CurrentZoom = self._CurrentZoom * zoomFactor

            if camera.GetParallelProjection():
                parallelScale = camera.GetParallelScale()/zoomFactor
                camera.SetParallelScale(parallelScale)
            else:
                camera.Dolly(zoomFactor)
                renderer.ResetCameraClippingRange()

            self._LastX = x
            self._LastY = y

            self.Render()


    def Reset(self):
        if self._CurrentRenderer:
            self._CurrentRenderer.ResetCamera()

        self.Render()


    def Wireframe(self):
        actors = self._CurrentRenderer.GetActors()
        numActors = actors.GetNumberOfItems()
        actors.InitTraversal()
        for i in range(0,numActors):
            actor = actors.GetNextItem()
            actor.GetProperty().SetRepresentationToWireframe()

        self.Render()


    def Surface(self):
        actors = self._CurrentRenderer.GetActors()
        numActors = actors.GetNumberOfItems()
        actors.InitTraversal()
        for i in range(0,numActors):
            actor = actors.GetNextItem()
            actor.GetProperty().SetRepresentationToSurface()

        self.Render()


    def PickActor(self,x,y):
        if self._CurrentRenderer:
            renderer = self._CurrentRenderer
            picker = self._Picker

            windowY = self.GetSize().height
            picker.Pick(x,(windowY - y - 1),0.0,renderer)
            assembly = picker.GetAssembly()

            if (self._PickedAssembly != None and
                self._PrePickedProperty != None):
                self._PickedAssembly.SetProperty(self._PrePickedProperty)
                # release hold of the property
                self._PrePickedProperty.UnRegister(self._PrePickedProperty)
                self._PrePickedProperty = None

            if (assembly != None):
                self._PickedAssembly = assembly
                self._PrePickedProperty = self._PickedAssembly.GetProperty()
                # hold onto the property
                self._PrePickedProperty.Register(self._PrePickedProperty)
                self._PickedAssembly.SetProperty(self._PickedProperty)

            self.Render()
