1"""
2A simple VTK widget for wxPython.
3
4Find wxPython info at http://wxPython.org
5
6Created by David Gobbi, December 2001
7Based on vtkTkRenderWindget.py
8
9Updated to new wx namespace and some cleaning by Andrea Gavana,
10December 2006
11
12"""
13
14"""
15Please see the example at the end of this file.
16
17----------------------------------------
18Creation:
19
20wxVTKRenderWindow(parent, ID, stereo=0, [wx keywords]):
21
22You should create a wx.App(False) or some other wx.App subclass
23before creating the window.
24
25----------------------------------------
26Methods:
27
28Render()
29AddRenderer(ren)
30GetRenderers()
31GetRenderWindow()
32
33----------------------------------------
34Methods to override (all take a wx.Event):
35
36OnButtonDown(event)  default: propagate event to Left, Right, Middle
37OnLeftDown(event)    default: set _Mode to 'Rotate'
38OnRightDown(event)   default: set _Mode to 'Zoom'
39OnMiddleDown(event)  default: set _Mode to 'Pan'
40
41OnButtonUp(event)    default: propagate event to L, R, M and unset _Mode
42OnLeftUp(event)
43OnRightUp(event)
44OnMiddleUp(event)
45
46OnMotion(event)      default: call appropriate handler for _Mode
47
48OnEnterWindow(event) default: set focus to this window
49OnLeaveWindow(event) default: release focus
50
51OnKeyDown(event)     default: [R]eset, [W]irefreme, [S]olid, [P]ick
52OnKeyUp(event)
53OnChar(event)
54
55OnSetFocus(event)
56OnKillFocus(event)
57
58OnSize(event)
59OnMove(event)
60
61OnPaint(event)       default: Render()
62
63----------------------------------------
64Protected Members:
65
66_Mode:                 Current mode: 'Rotate', 'Zoom', 'Pan'
67_LastX, _LastY:        The (x,y) coordinates of the previous event
68_CurrentRenderer:      The renderer that was most recently clicked in
69_CurrentCamera:        The camera for the current renderer
70
71----------------------------------------
72Private Members:
73
74__Handle:              Handle to the window containing the vtkRenderWindow
75
76"""
77
78# import usual libraries
79import math, os, sys
80import wx
81from vtkmodules.vtkRenderingCore import vtkCellPicker, vtkProperty, vtkRenderWindow
82
83# a few configuration items, see what works best on your system
84
85# Use GLCanvas as base class instead of wx.Window.
86# This is sometimes necessary under wxGTK or the image is blank.
87# (in wxWindows 2.3.1 and earlier, the GLCanvas had scroll bars)
88baseClass = wx.Window
89if wx.Platform == "__WXGTK__":
90    import wx.glcanvas
91    baseClass = wx.glcanvas.GLCanvas
92
93# Keep capturing mouse after mouse is dragged out of window
94# (in wxGTK 2.3.2 there is a bug that keeps this from working,
95# but it is only relevant in wxGTK if there are multiple windows)
96_useCapture = (wx.Platform == "__WXMSW__")
97
98# end of configuration items
99
100
101class wxVTKRenderWindow(baseClass):
102    """
103    A wxRenderWindow for wxPython.
104    Use GetRenderWindow() to get the vtkRenderWindow.
105    Create with the keyword stereo=1 in order to
106    generate a stereo-capable window.
107    """
108
109    def __init__(self, parent, ID, *args, **kw):
110        """Default class constructor.
111        @param parent: parent window
112        @param ID: window id
113        @param **kw: wxPython keywords (position, size, style) plus the
114        'stereo' keyword
115        """
116        # miscellaneous protected variables
117        self._CurrentRenderer = None
118        self._CurrentCamera = None
119        self._CurrentZoom = 1.0
120        self._CurrentLight = None
121
122        self._ViewportCenterX = 0
123        self._ViewportCenterY = 0
124
125        self._Picker = vtkCellPicker()
126        self._PickedActor = None
127        self._PickedProperty = vtkProperty()
128        self._PickedProperty.SetColor(1,0,0)
129        self._PrePickedProperty = None
130
131        # these record the previous mouse position
132        self._LastX = 0
133        self._LastY = 0
134
135        # the current interaction mode (Rotate, Pan, Zoom, etc)
136        self._Mode = None
137        self._ActiveButton = None
138
139        # private attributes
140        self.__OldFocus = None
141
142        # used by the LOD actors
143        self._DesiredUpdateRate = 15
144        self._StillUpdateRate = 0.0001
145
146        # First do special handling of some keywords:
147        # stereo, position, size, width, height, style
148
149        try:
150            stereo = bool(kw['stereo'])
151            del kw['stereo']
152        except KeyError:
153            stereo = False
154
155        try:
156            position = kw['position']
157            del kw['position']
158        except KeyError:
159            position = wx.DefaultPosition
160
161        try:
162            size = kw['size']
163            del kw['size']
164        except KeyError:
165            try:
166                size = parent.GetSize()
167            except AttributeError:
168                size = wx.DefaultSize
169
170        # wx.WANTS_CHARS says to give us e.g. TAB
171        # wx.NO_FULL_REPAINT_ON_RESIZE cuts down resize flicker under GTK
172        style = wx.WANTS_CHARS | wx.NO_FULL_REPAINT_ON_RESIZE
173
174        try:
175            style = style | kw['style']
176            del kw['style']
177        except KeyError:
178            pass
179
180        # the enclosing frame must be shown under GTK or the windows
181        #  don't connect together properly
182        l = []
183        p = parent
184        while p: # make a list of all parents
185            l.append(p)
186            p = p.GetParent()
187        l.reverse() # sort list into descending order
188        for p in l:
189            p.Show(1)
190
191        # initialize the wx.Window
192        if baseClass.__name__ == 'GLCanvas':
193            # Set the doublebuffer attribute of the GL canvas.
194            baseClass.__init__(self, parent, ID, pos=position, size=size,
195                               style=style,
196                               attribList=[wx.glcanvas.WX_GL_DOUBLEBUFFER])
197        else:
198            baseClass.__init__(self, parent, ID, pos=position, size=size,
199                               style=style)
200
201        # create the RenderWindow and initialize it
202        self._RenderWindow = vtkRenderWindow()
203        self._RenderWindow.SetSize(size.width, size.height)
204
205        if stereo:
206            self._RenderWindow.StereoCapableWindowOn()
207            self._RenderWindow.SetStereoTypeToCrystalEyes()
208
209        self.__handle = None
210
211        # refresh window by doing a Render
212        self.Bind(wx.EVT_PAINT, self.OnPaint)
213        # turn off background erase to reduce flicker
214        self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None)
215
216        # Bind the events to the event converters
217        self.Bind(wx.EVT_RIGHT_DOWN, self._OnButtonDown)
218        self.Bind(wx.EVT_LEFT_DOWN, self._OnButtonDown)
219        self.Bind(wx.EVT_MIDDLE_DOWN, self._OnButtonDown)
220        self.Bind(wx.EVT_RIGHT_UP, self._OnButtonUp)
221        self.Bind(wx.EVT_LEFT_UP, self._OnButtonUp)
222        self.Bind(wx.EVT_MIDDLE_UP, self._OnButtonUp)
223        self.Bind(wx.EVT_MOTION, self.OnMotion)
224
225        self.Bind(wx.EVT_ENTER_WINDOW, self._OnEnterWindow)
226        self.Bind(wx.EVT_LEAVE_WINDOW, self._OnLeaveWindow)
227
228        self.Bind(wx.EVT_CHAR, self.OnChar)
229
230        # If we use EVT_KEY_DOWN instead of EVT_CHAR, capital versions
231        # of all characters are always returned.  EVT_CHAR also performs
232        # other necessary keyboard-dependent translations.
233        self.Bind(wx.EVT_CHAR, self.OnKeyDown)
234        self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
235
236        self.Bind(wx.EVT_SIZE, self._OnSize)
237        self.Bind(wx.EVT_MOVE, self.OnMove)
238
239        self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
240        self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
241
242    def SetDesiredUpdateRate(self, rate):
243        """Mirrors the method with the same name in
244        vtkRenderWindowInteractor.
245        """
246        self._DesiredUpdateRate = rate
247
248    def GetDesiredUpdateRate(self):
249        """Mirrors the method with the same name in
250        vtkRenderWindowInteractor.
251        """
252        return self._DesiredUpdateRate
253
254    def SetStillUpdateRate(self, rate):
255        """Mirrors the method with the same name in
256        vtkRenderWindowInteractor.
257        """
258        self._StillUpdateRate = rate
259
260    def GetStillUpdateRate(self):
261        """Mirrors the method with the same name in
262        vtkRenderWindowInteractor.
263        """
264        return self._StillUpdateRate
265
266    def OnPaint(self, event):
267        """Handles the wx.EVT_PAINT event for wxVTKRenderWindow.
268        """
269        dc = wx.PaintDC(self)
270        self.Render()
271
272    def _OnSize(self, event):
273        """Handles the wx.EVT_SIZE event for wxVTKRenderWindow.
274        """
275        if wx.Platform != '__WXMSW__':
276            width, height = event.GetSize()
277            self._RenderWindow.SetSize(width, height)
278        self.OnSize(event)
279        self.Render()
280
281    def OnSize(self, event):
282        """Overridable event.
283        """
284        pass
285
286    def OnMove(self, event):
287        """Overridable event.
288        """
289        pass
290
291
292    def _OnEnterWindow(self, event):
293        """Handles the wx.EVT_ENTER_WINDOW event for
294        wxVTKRenderWindow.
295        """
296        self.UpdateRenderer(event)
297        self.OnEnterWindow(event)
298
299
300    def OnEnterWindow(self, event):
301        """Overridable event.
302        """
303        if self.__OldFocus == None:
304            self.__OldFocus = wx.Window.FindFocus()
305            self.SetFocus()
306
307    def _OnLeaveWindow(self, event):
308        """Handles the wx.EVT_LEAVE_WINDOW event for
309        wxVTKRenderWindow.
310        """
311        self.OnLeaveWindow(event)
312
313    def OnLeaveWindow(self, event):
314        """Overridable event.
315        """
316        if self.__OldFocus:
317            self.__OldFocus.SetFocus()
318            self.__OldFocus = None
319
320    def OnSetFocus(self, event):
321        """Overridable event.
322        """
323        pass
324
325    def OnKillFocus(self, event):
326        """Overridable event.
327        """
328        pass
329
330    def _OnButtonDown(self, event):
331        """Handles the wx.EVT_LEFT/RIGHT/MIDDLE_DOWN events for
332        wxVTKRenderWindow.
333        """
334        # helper function for capturing mouse until button released
335        self._RenderWindow.SetDesiredUpdateRate(self._DesiredUpdateRate)
336
337        if event.RightDown():
338            button = "Right"
339        elif event.LeftDown():
340            button = "Left"
341        elif event.MiddleDown():
342            button = "Middle"
343        else:
344            button = None
345
346        # save the button and capture mouse until the button is released
347        if button and not self._ActiveButton:
348            self._ActiveButton = button
349            if _useCapture:
350                self.CaptureMouse()
351
352        self.OnButtonDown(event)
353
354    def OnButtonDown(self, event):
355        """Overridable event.
356        """
357        if not self._Mode:
358            # figure out what renderer the mouse is over
359            self.UpdateRenderer(event)
360
361        if event.LeftDown():
362            self.OnLeftDown(event)
363        elif event.RightDown():
364            self.OnRightDown(event)
365        elif event.MiddleDown():
366            self.OnMiddleDown(event)
367
368    def OnLeftDown(self, event):
369        """Overridable event.
370        """
371        if not self._Mode:
372            if event.ControlDown():
373                self._Mode = "Zoom"
374            elif event.ShiftDown():
375                self._Mode = "Pan"
376            else:
377                self._Mode = "Rotate"
378
379    def OnRightDown(self, event):
380        """Overridable event.
381        """
382        if not self._Mode:
383            self._Mode = "Zoom"
384
385    def OnMiddleDown(self, event):
386        """Overridable event.
387        """
388        if not self._Mode:
389            self._Mode = "Pan"
390
391    def _OnButtonUp(self, event):
392        """Handles the wx.EVT_LEFT/RIGHT/MIDDLE_UP events for
393        wxVTKRenderWindow.
394        """
395        # helper function for releasing mouse capture
396        self._RenderWindow.SetDesiredUpdateRate(self._StillUpdateRate)
397
398        if event.RightUp():
399            button = "Right"
400        elif event.LeftUp():
401            button = "Left"
402        elif event.MiddleUp():
403            button = "Middle"
404        else:
405            button = None
406
407        # if the ActiveButton is released, then release mouse capture
408        if self._ActiveButton and button == self._ActiveButton:
409            if _useCapture:
410                self.ReleaseMouse()
411            self._ActiveButton = None
412
413        self.OnButtonUp(event)
414
415    def OnButtonUp(self, event):
416        """Overridable event.
417        """
418        if event.LeftUp():
419            self.OnLeftUp(event)
420        elif event.RightUp():
421            self.OnRightUp(event)
422        elif event.MiddleUp():
423            self.OnMiddleUp(event)
424
425        # if not interacting, then do nothing more
426        if self._Mode:
427            if self._CurrentRenderer:
428                self.Render()
429
430        self._Mode = None
431
432    def OnLeftUp(self, event):
433        """Overridable event.
434        """
435        pass
436
437    def OnRightUp(self, event):
438        """Overridable event.
439        """
440        pass
441
442    def OnMiddleUp(self, event):
443        """Overridable event.
444        """
445        pass
446
447    def OnMotion(self, event):
448        """Overridable event.
449        """
450        if self._Mode == "Pan":
451            self.Pan(event)
452        elif self._Mode == "Rotate":
453            self.Rotate(event)
454        elif self._Mode == "Zoom":
455            self.Zoom(event)
456
457    def OnChar(self, event):
458        """Overridable event.
459        """
460        pass
461
462    def OnKeyDown(self, event):
463        """Handles the wx.EVT_KEY_DOWN events for wxVTKRenderWindow.
464        """
465        if event.GetKeyCode() == ord('r'):
466            self.Reset(event)
467        if event.GetKeyCode() == ord('w'):
468            self.Wireframe()
469        if event.GetKeyCode() == ord('s'):
470            self.Surface()
471        if event.GetKeyCode() == ord('p'):
472            self.PickActor(event)
473
474        if event.GetKeyCode() < 256:
475            self.OnChar(event)
476
477    def OnKeyUp(self, event):
478        """Overridable event.
479        """
480        pass
481
482    def GetZoomFactor(self):
483        """Returns the current zoom factor.
484        """
485        return self._CurrentZoom
486
487    def GetRenderWindow(self):
488        """Returns the render window (vtkRenderWindow).
489        """
490        return self._RenderWindow
491
492    def GetPicker(self):
493        """Returns the current picker (vtkCellPicker).
494        """
495        return self._Picker
496
497    def Render(self):
498        """Actually renders the VTK scene on screen.
499        """
500        if self._CurrentLight:
501            light = self._CurrentLight
502            light.SetPosition(self._CurrentCamera.GetPosition())
503            light.SetFocalPoint(self._CurrentCamera.GetFocalPoint())
504
505        if not self.GetUpdateRegion().IsEmpty() or self.__handle:
506            if self.__handle and self.__handle == self.GetHandle():
507                self._RenderWindow.Render()
508
509            elif self.GetHandle():
510                # this means the user has reparented us
511                # let's adapt to the new situation by doing the WindowRemap
512                # dance
513                self._RenderWindow.SetNextWindowInfo(str(self.GetHandle()))
514                self._RenderWindow.WindowRemap()
515                # store the new situation
516                self.__handle = self.GetHandle()
517
518                self._RenderWindow.Render()
519
520    def UpdateRenderer(self, event):
521        """
522        UpdateRenderer will identify the renderer under the mouse and set
523        up _CurrentRenderer, _CurrentCamera, and _CurrentLight.
524        """
525        x = event.GetX()
526        y = event.GetY()
527        windowX, windowY = self._RenderWindow.GetSize()
528
529        renderers = self._RenderWindow.GetRenderers()
530        numRenderers = renderers.GetNumberOfItems()
531
532        self._CurrentRenderer = None
533        renderers.InitTraversal()
534        for i in range(0,numRenderers):
535            renderer = renderers.GetNextItem()
536            vx,vy = (0,0)
537            if (windowX > 1):
538                vx = float(x)/(windowX-1)
539            if (windowY > 1):
540                vy = (windowY-float(y)-1)/(windowY-1)
541            (vpxmin,vpymin,vpxmax,vpymax) = renderer.GetViewport()
542
543            if (vx >= vpxmin and vx <= vpxmax and
544                vy >= vpymin and vy <= vpymax):
545                self._CurrentRenderer = renderer
546                self._ViewportCenterX = float(windowX)*(vpxmax-vpxmin)/2.0\
547                                        +vpxmin
548                self._ViewportCenterY = float(windowY)*(vpymax-vpymin)/2.0\
549                                        +vpymin
550                self._CurrentCamera = self._CurrentRenderer.GetActiveCamera()
551                lights = self._CurrentRenderer.GetLights()
552                lights.InitTraversal()
553                self._CurrentLight = lights.GetNextItem()
554                break
555
556        self._LastX = x
557        self._LastY = y
558
559    def GetCurrentRenderer(self):
560        """Returns the current renderer.
561        """
562        return self._CurrentRenderer
563
564    def Rotate(self, event):
565        """Rotates the scene (camera).
566        """
567        if self._CurrentRenderer:
568            x = event.GetX()
569            y = event.GetY()
570
571            self._CurrentCamera.Azimuth(self._LastX - x)
572            self._CurrentCamera.Elevation(y - self._LastY)
573            self._CurrentCamera.OrthogonalizeViewUp()
574
575            self._LastX = x
576            self._LastY = y
577
578            self._CurrentRenderer.ResetCameraClippingRange()
579            self.Render()
580
581    def Pan(self, event):
582        """Pans the scene (camera).
583        """
584        if self._CurrentRenderer:
585            x = event.GetX()
586            y = event.GetY()
587
588            renderer = self._CurrentRenderer
589            camera = self._CurrentCamera
590            (pPoint0,pPoint1,pPoint2) = camera.GetPosition()
591            (fPoint0,fPoint1,fPoint2) = camera.GetFocalPoint()
592
593            if camera.GetParallelProjection():
594                renderer.SetWorldPoint(fPoint0,fPoint1,fPoint2,1.0)
595                renderer.WorldToDisplay()
596                fx,fy,fz = renderer.GetDisplayPoint()
597                renderer.SetDisplayPoint(fx-x+self._LastX,
598                                         fy+y-self._LastY,
599                                         fz)
600                renderer.DisplayToWorld()
601                fx,fy,fz,fw = renderer.GetWorldPoint()
602                camera.SetFocalPoint(fx,fy,fz)
603
604                renderer.SetWorldPoint(pPoint0,pPoint1,pPoint2,1.0)
605                renderer.WorldToDisplay()
606                fx,fy,fz = renderer.GetDisplayPoint()
607                renderer.SetDisplayPoint(fx-x+self._LastX,
608                                         fy+y-self._LastY,
609                                         fz)
610                renderer.DisplayToWorld()
611                fx,fy,fz,fw = renderer.GetWorldPoint()
612                camera.SetPosition(fx,fy,fz)
613
614            else:
615                (fPoint0,fPoint1,fPoint2) = camera.GetFocalPoint()
616                # Specify a point location in world coordinates
617                renderer.SetWorldPoint(fPoint0,fPoint1,fPoint2,1.0)
618                renderer.WorldToDisplay()
619                # Convert world point coordinates to display coordinates
620                dPoint = renderer.GetDisplayPoint()
621                focalDepth = dPoint[2]
622
623                aPoint0 = self._ViewportCenterX + (x - self._LastX)
624                aPoint1 = self._ViewportCenterY - (y - self._LastY)
625
626                renderer.SetDisplayPoint(aPoint0,aPoint1,focalDepth)
627                renderer.DisplayToWorld()
628
629                (rPoint0,rPoint1,rPoint2,rPoint3) = renderer.GetWorldPoint()
630                if (rPoint3 != 0.0):
631                    rPoint0 = rPoint0/rPoint3
632                    rPoint1 = rPoint1/rPoint3
633                    rPoint2 = rPoint2/rPoint3
634
635                camera.SetFocalPoint((fPoint0 - rPoint0) + fPoint0,
636                                     (fPoint1 - rPoint1) + fPoint1,
637                                     (fPoint2 - rPoint2) + fPoint2)
638
639                camera.SetPosition((fPoint0 - rPoint0) + pPoint0,
640                                   (fPoint1 - rPoint1) + pPoint1,
641                                   (fPoint2 - rPoint2) + pPoint2)
642
643            self._LastX = x
644            self._LastY = y
645
646            self.Render()
647
648    def Zoom(self, event):
649        """Zooms the scene (camera).
650        """
651        if self._CurrentRenderer:
652            x = event.GetX()
653            y = event.GetY()
654
655            renderer = self._CurrentRenderer
656            camera = self._CurrentCamera
657
658            zoomFactor = math.pow(1.02,(0.5*(self._LastY - y)))
659            self._CurrentZoom = self._CurrentZoom * zoomFactor
660
661            if camera.GetParallelProjection():
662                parallelScale = camera.GetParallelScale()/zoomFactor
663                camera.SetParallelScale(parallelScale)
664            else:
665                camera.Dolly(zoomFactor)
666                renderer.ResetCameraClippingRange()
667
668            self._LastX = x
669            self._LastY = y
670
671            self.Render()
672
673    def Reset(self, event=None):
674        """Resets the camera.
675        """
676        if self._CurrentRenderer:
677            self._CurrentRenderer.ResetCamera()
678
679        self.Render()
680
681    def Wireframe(self):
682        """Sets the current actor representation as wireframe.
683        """
684        actors = self._CurrentRenderer.GetActors()
685        numActors = actors.GetNumberOfItems()
686        actors.InitTraversal()
687        for i in range(0,numActors):
688            actor = actors.GetNextItem()
689            actor.GetProperty().SetRepresentationToWireframe()
690
691        self.Render()
692
693    def Surface(self):
694        """Sets the current actor representation as surface.
695        """
696        actors = self._CurrentRenderer.GetActors()
697        numActors = actors.GetNumberOfItems()
698        actors.InitTraversal()
699        for i in range(0,numActors):
700            actor = actors.GetNextItem()
701            actor.GetProperty().SetRepresentationToSurface()
702
703        self.Render()
704
705    def PickActor(self, event):
706        """Picks an actor.
707        """
708        if self._CurrentRenderer:
709            x = event.GetX()
710            y = event.GetY()
711
712            renderer = self._CurrentRenderer
713            picker = self._Picker
714
715            windowX, windowY = self._RenderWindow.GetSize()
716            picker.Pick(x,(windowY - y - 1),0.0,renderer)
717            actor = picker.GetActor()
718
719            if (self._PickedActor != None and
720                self._PrePickedProperty != None):
721                self._PickedActor.SetProperty(self._PrePickedProperty)
722                # release hold of the property
723                self._PrePickedProperty.UnRegister(self._PrePickedProperty)
724                self._PrePickedProperty = None
725
726            if (actor != None):
727                self._PickedActor = actor
728                self._PrePickedProperty = self._PickedActor.GetProperty()
729                # hold onto the property
730                self._PrePickedProperty.Register(self._PrePickedProperty)
731                self._PickedActor.SetProperty(self._PickedProperty)
732
733            self.Render()
734
735
736#----------------------------------------------------------------------------
737def wxVTKRenderWindowConeExample():
738    """Like it says, just a simple example.
739    """
740
741    from vtkmodules.vtkFiltersSources import vtkConeSource
742    from vtkmodules.vtkRenderingCore import vtkActor, vtkPolyDataMapper, vtkRenderer
743
744    # every wx app needs an app
745    app = wx.App(False)
746
747    # create the widget
748    frame = wx.Frame(None, -1, "wxVTKRenderWindow", size=(400,400))
749    widget = wxVTKRenderWindow(frame, -1)
750
751    ren = vtkRenderer()
752    widget.GetRenderWindow().AddRenderer(ren)
753
754    cone = vtkConeSource()
755    cone.SetResolution(8)
756
757    coneMapper = vtkPolyDataMapper()
758    coneMapper.SetInputConnection(cone.GetOutputPort())
759
760    coneActor = vtkActor()
761    coneActor.SetMapper(coneMapper)
762
763    ren.AddActor(coneActor)
764
765    # show the window
766
767    frame.Show()
768
769    app.MainLoop()
770
771if __name__ == "__main__":
772    wxVTKRenderWindowConeExample()
773