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
81import vtk
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 = vtk.vtkCellPicker()
126        self._PickedActor = None
127        self._PickedProperty = vtk.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        stereo = 0
150
151        if kw.has_key('stereo'):
152            if kw['stereo']:
153                stereo = 1
154            del kw['stereo']
155
156        position = wx.DefaultPosition
157
158        if kw.has_key('position'):
159            position = kw['position']
160            del kw['position']
161
162        try:
163            size = parent.GetSize()
164        except AttributeError:
165            size = wx.DefaultSize
166
167        if kw.has_key('size'):
168            size = kw['size']
169            del kw['size']
170
171        # wx.WANTS_CHARS says to give us e.g. TAB
172        # wx.NO_FULL_REPAINT_ON_RESIZE cuts down resize flicker under GTK
173        style = wx.WANTS_CHARS | wx.NO_FULL_REPAINT_ON_RESIZE
174
175        if kw.has_key('style'):
176            style = style | kw['style']
177            del kw['style']
178
179        # the enclosing frame must be shown under GTK or the windows
180        #  don't connect together properly
181        l = []
182        p = parent
183        while p: # make a list of all parents
184            l.append(p)
185            p = p.GetParent()
186        l.reverse() # sort list into descending order
187        for p in l:
188            p.Show(1)
189
190        # initialize the wx.Window
191        if baseClass.__name__ == 'GLCanvas':
192            # Set the doublebuffer attribute of the GL canvas.
193            baseClass.__init__(self, parent, ID, pos=position, size=size,
194                               style=style,
195                               attribList=[wx.glcanvas.WX_GL_DOUBLEBUFFER])
196        else:
197            baseClass.__init__(self, parent, ID, pos=position, size=size,
198                               style=style)
199
200        # create the RenderWindow and initialize it
201        self._RenderWindow = vtk.vtkRenderWindow()
202        self._RenderWindow.SetSize(size.width, size.height)
203
204        if stereo:
205            self._RenderWindow.StereoCapableWindowOn()
206            self._RenderWindow.SetStereoTypeToCrystalEyes()
207
208        self.__handle = None
209
210        # refresh window by doing a Render
211        self.Bind(wx.EVT_PAINT, self.OnPaint)
212        # turn off background erase to reduce flicker
213        self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None)
214
215        # Bind the events to the event converters
216        self.Bind(wx.EVT_RIGHT_DOWN, self._OnButtonDown)
217        self.Bind(wx.EVT_LEFT_DOWN, self._OnButtonDown)
218        self.Bind(wx.EVT_MIDDLE_DOWN, self._OnButtonDown)
219        self.Bind(wx.EVT_RIGHT_UP, self._OnButtonUp)
220        self.Bind(wx.EVT_LEFT_UP, self._OnButtonUp)
221        self.Bind(wx.EVT_MIDDLE_UP, self._OnButtonUp)
222        self.Bind(wx.EVT_MOTION, self.OnMotion)
223
224        self.Bind(wx.EVT_ENTER_WINDOW, self._OnEnterWindow)
225        self.Bind(wx.EVT_LEAVE_WINDOW, self._OnLeaveWindow)
226
227        self.Bind(wx.EVT_CHAR, self.OnChar)
228
229        # If we use EVT_KEY_DOWN instead of EVT_CHAR, capital versions
230        # of all characters are always returned.  EVT_CHAR also performs
231        # other necessary keyboard-dependent translations.
232        self.Bind(wx.EVT_CHAR, self.OnKeyDown)
233        self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
234
235        self.Bind(wx.EVT_SIZE, self._OnSize)
236        self.Bind(wx.EVT_MOVE, self.OnMove)
237
238        self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
239        self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
240
241    def SetDesiredUpdateRate(self, rate):
242        """Mirrors the method with the same name in
243        vtkRenderWindowInteractor.
244        """
245        self._DesiredUpdateRate = rate
246
247    def GetDesiredUpdateRate(self):
248        """Mirrors the method with the same name in
249        vtkRenderWindowInteractor.
250        """
251        return self._DesiredUpdateRate
252
253    def SetStillUpdateRate(self, rate):
254        """Mirrors the method with the same name in
255        vtkRenderWindowInteractor.
256        """
257        self._StillUpdateRate = rate
258
259    def GetStillUpdateRate(self):
260        """Mirrors the method with the same name in
261        vtkRenderWindowInteractor.
262        """
263        return self._StillUpdateRate
264
265    def OnPaint(self, event):
266        """Handles the wx.EVT_PAINT event for wxVTKRenderWindow.
267        """
268        dc = wx.PaintDC(self)
269        self.Render()
270
271    def _OnSize(self, event):
272        """Handles the wx.EVT_SIZE event for wxVTKRenderWindow.
273        """
274        if wx.Platform != '__WXMSW__':
275            width, height = event.GetSize()
276            self._RenderWindow.SetSize(width, height)
277        self.OnSize(event)
278        self.Render()
279
280    def OnSize(self, event):
281        """Overridable event.
282        """
283        pass
284
285    def OnMove(self, event):
286        """Overridable event.
287        """
288        pass
289
290
291    def _OnEnterWindow(self, event):
292        """Handles the wx.EVT_ENTER_WINDOW event for
293        wxVTKRenderWindow.
294        """
295        self.UpdateRenderer(event)
296        self.OnEnterWindow(event)
297
298
299    def OnEnterWindow(self, event):
300        """Overridable event.
301        """
302        if self.__OldFocus == None:
303            self.__OldFocus = wx.Window.FindFocus()
304            self.SetFocus()
305
306    def _OnLeaveWindow(self, event):
307        """Handles the wx.EVT_LEAVE_WINDOW event for
308        wxVTKRenderWindow.
309        """
310        self.OnLeaveWindow(event)
311
312    def OnLeaveWindow(self, event):
313        """Overridable event.
314        """
315        if self.__OldFocus:
316            self.__OldFocus.SetFocus()
317            self.__OldFocus = None
318
319    def OnSetFocus(self, event):
320        """Overridable event.
321        """
322        pass
323
324    def OnKillFocus(self, event):
325        """Overridable event.
326        """
327        pass
328
329    def _OnButtonDown(self, event):
330        """Handles the wx.EVT_LEFT/RIGHT/MIDDLE_DOWN events for
331        wxVTKRenderWindow.
332        """
333        # helper function for capturing mouse until button released
334        self._RenderWindow.SetDesiredUpdateRate(self._DesiredUpdateRate)
335
336        if event.RightDown():
337            button = "Right"
338        elif event.LeftDown():
339            button = "Left"
340        elif event.MiddleDown():
341            button = "Middle"
342        else:
343            button = None
344
345        # save the button and capture mouse until the button is released
346        if button and not self._ActiveButton:
347            self._ActiveButton = button
348            if _useCapture:
349                self.CaptureMouse()
350
351        self.OnButtonDown(event)
352
353    def OnButtonDown(self, event):
354        """Overridable event.
355        """
356        if not self._Mode:
357            # figure out what renderer the mouse is over
358            self.UpdateRenderer(event)
359
360        if event.LeftDown():
361            self.OnLeftDown(event)
362        elif event.RightDown():
363            self.OnRightDown(event)
364        elif event.MiddleDown():
365            self.OnMiddleDown(event)
366
367    def OnLeftDown(self, event):
368        """Overridable event.
369        """
370        if not self._Mode:
371            if event.ControlDown():
372                self._Mode = "Zoom"
373            elif event.ShiftDown():
374                self._Mode = "Pan"
375            else:
376                self._Mode = "Rotate"
377
378    def OnRightDown(self, event):
379        """Overridable event.
380        """
381        if not self._Mode:
382            self._Mode = "Zoom"
383
384    def OnMiddleDown(self, event):
385        """Overridable event.
386        """
387        if not self._Mode:
388            self._Mode = "Pan"
389
390    def _OnButtonUp(self, event):
391        """Handles the wx.EVT_LEFT/RIGHT/MIDDLE_UP events for
392        wxVTKRenderWindow.
393        """
394        # helper function for releasing mouse capture
395        self._RenderWindow.SetDesiredUpdateRate(self._StillUpdateRate)
396
397        if event.RightUp():
398            button = "Right"
399        elif event.LeftUp():
400            button = "Left"
401        elif event.MiddleUp():
402            button = "Middle"
403        else:
404            button = None
405
406        # if the ActiveButton is realeased, then release mouse capture
407        if self._ActiveButton and button == self._ActiveButton:
408            if _useCapture:
409                self.ReleaseMouse()
410            self._ActiveButton = None
411
412        self.OnButtonUp(event)
413
414    def OnButtonUp(self, event):
415        """Overridable event.
416        """
417        if event.LeftUp():
418            self.OnLeftUp(event)
419        elif event.RightUp():
420            self.OnRightUp(event)
421        elif event.MiddleUp():
422            self.OnMiddleUp(event)
423
424        # if not interacting, then do nothing more
425        if self._Mode:
426            if self._CurrentRenderer:
427                self.Render()
428
429        self._Mode = None
430
431    def OnLeftUp(self, event):
432        """Overridable event.
433        """
434        pass
435
436    def OnRightUp(self, event):
437        """Overridable event.
438        """
439        pass
440
441    def OnMiddleUp(self, event):
442        """Overridable event.
443        """
444        pass
445
446    def OnMotion(self, event):
447        """Overridable event.
448        """
449        if self._Mode == "Pan":
450            self.Pan(event)
451        elif self._Mode == "Rotate":
452            self.Rotate(event)
453        elif self._Mode == "Zoom":
454            self.Zoom(event)
455
456    def OnChar(self, event):
457        """Overridable event.
458        """
459        pass
460
461    def OnKeyDown(self, event):
462        """Handles the wx.EVT_KEY_DOWN events for wxVTKRenderWindow.
463        """
464        if event.GetKeyCode() == ord('r'):
465            self.Reset(event)
466        if event.GetKeyCode() == ord('w'):
467            self.Wireframe()
468        if event.GetKeyCode() == ord('s'):
469            self.Surface()
470        if event.GetKeyCode() == ord('p'):
471            self.PickActor(event)
472
473        if event.GetKeyCode() < 256:
474            self.OnChar(event)
475
476    def OnKeyUp(self, event):
477        """Overridable event.
478        """
479        pass
480
481    def GetZoomFactor(self):
482        """Returns the current zoom factor.
483        """
484        return self._CurrentZoom
485
486    def GetRenderWindow(self):
487        """Returns the render window (vtkRenderWindow).
488        """
489        return self._RenderWindow
490
491    def GetPicker(self):
492        """Returns the current picker (vtkCellPicker).
493        """
494        return self._Picker
495
496    def Render(self):
497        """Actually renders the VTK scene on screen.
498        """
499        if self._CurrentLight:
500            light = self._CurrentLight
501            light.SetPosition(self._CurrentCamera.GetPosition())
502            light.SetFocalPoint(self._CurrentCamera.GetFocalPoint())
503
504        if not self.GetUpdateRegion().IsEmpty() or self.__handle:
505            if self.__handle and self.__handle == self.GetHandle():
506                self._RenderWindow.Render()
507
508            elif self.GetHandle():
509                # this means the user has reparented us
510                # let's adapt to the new situation by doing the WindowRemap
511                # dance
512                self._RenderWindow.SetNextWindowInfo(str(self.GetHandle()))
513                self._RenderWindow.WindowRemap()
514                # store the new situation
515                self.__handle = self.GetHandle()
516
517                self._RenderWindow.Render()
518
519    def UpdateRenderer(self, event):
520        """
521        UpdateRenderer will identify the renderer under the mouse and set
522        up _CurrentRenderer, _CurrentCamera, and _CurrentLight.
523        """
524        x = event.GetX()
525        y = event.GetY()
526        windowX, windowY = self._RenderWindow.GetSize()
527
528        renderers = self._RenderWindow.GetRenderers()
529        numRenderers = renderers.GetNumberOfItems()
530
531        self._CurrentRenderer = None
532        renderers.InitTraversal()
533        for i in range(0,numRenderers):
534            renderer = renderers.GetNextItem()
535            vx,vy = (0,0)
536            if (windowX > 1):
537                vx = float(x)/(windowX-1)
538            if (windowY > 1):
539                vy = (windowY-float(y)-1)/(windowY-1)
540            (vpxmin,vpymin,vpxmax,vpymax) = renderer.GetViewport()
541
542            if (vx >= vpxmin and vx <= vpxmax and
543                vy >= vpymin and vy <= vpymax):
544                self._CurrentRenderer = renderer
545                self._ViewportCenterX = float(windowX)*(vpxmax-vpxmin)/2.0\
546                                        +vpxmin
547                self._ViewportCenterY = float(windowY)*(vpymax-vpymin)/2.0\
548                                        +vpymin
549                self._CurrentCamera = self._CurrentRenderer.GetActiveCamera()
550                lights = self._CurrentRenderer.GetLights()
551                lights.InitTraversal()
552                self._CurrentLight = lights.GetNextItem()
553                break
554
555        self._LastX = x
556        self._LastY = y
557
558    def GetCurrentRenderer(self):
559        """Returns the current renderer.
560        """
561        return self._CurrentRenderer
562
563    def Rotate(self, event):
564        """Rotates the scene (camera).
565        """
566        if self._CurrentRenderer:
567            x = event.GetX()
568            y = event.GetY()
569
570            self._CurrentCamera.Azimuth(self._LastX - x)
571            self._CurrentCamera.Elevation(y - self._LastY)
572            self._CurrentCamera.OrthogonalizeViewUp()
573
574            self._LastX = x
575            self._LastY = y
576
577            self._CurrentRenderer.ResetCameraClippingRange()
578            self.Render()
579
580    def Pan(self, event):
581        """Pans the scene (camera).
582        """
583        if self._CurrentRenderer:
584            x = event.GetX()
585            y = event.GetY()
586
587            renderer = self._CurrentRenderer
588            camera = self._CurrentCamera
589            (pPoint0,pPoint1,pPoint2) = camera.GetPosition()
590            (fPoint0,fPoint1,fPoint2) = camera.GetFocalPoint()
591
592            if camera.GetParallelProjection():
593                renderer.SetWorldPoint(fPoint0,fPoint1,fPoint2,1.0)
594                renderer.WorldToDisplay()
595                fx,fy,fz = renderer.GetDisplayPoint()
596                renderer.SetDisplayPoint(fx-x+self._LastX,
597                                         fy+y-self._LastY,
598                                         fz)
599                renderer.DisplayToWorld()
600                fx,fy,fz,fw = renderer.GetWorldPoint()
601                camera.SetFocalPoint(fx,fy,fz)
602
603                renderer.SetWorldPoint(pPoint0,pPoint1,pPoint2,1.0)
604                renderer.WorldToDisplay()
605                fx,fy,fz = renderer.GetDisplayPoint()
606                renderer.SetDisplayPoint(fx-x+self._LastX,
607                                         fy+y-self._LastY,
608                                         fz)
609                renderer.DisplayToWorld()
610                fx,fy,fz,fw = renderer.GetWorldPoint()
611                camera.SetPosition(fx,fy,fz)
612
613            else:
614                (fPoint0,fPoint1,fPoint2) = camera.GetFocalPoint()
615                # Specify a point location in world coordinates
616                renderer.SetWorldPoint(fPoint0,fPoint1,fPoint2,1.0)
617                renderer.WorldToDisplay()
618                # Convert world point coordinates to display coordinates
619                dPoint = renderer.GetDisplayPoint()
620                focalDepth = dPoint[2]
621
622                aPoint0 = self._ViewportCenterX + (x - self._LastX)
623                aPoint1 = self._ViewportCenterY - (y - self._LastY)
624
625                renderer.SetDisplayPoint(aPoint0,aPoint1,focalDepth)
626                renderer.DisplayToWorld()
627
628                (rPoint0,rPoint1,rPoint2,rPoint3) = renderer.GetWorldPoint()
629                if (rPoint3 != 0.0):
630                    rPoint0 = rPoint0/rPoint3
631                    rPoint1 = rPoint1/rPoint3
632                    rPoint2 = rPoint2/rPoint3
633
634                camera.SetFocalPoint((fPoint0 - rPoint0) + fPoint0,
635                                     (fPoint1 - rPoint1) + fPoint1,
636                                     (fPoint2 - rPoint2) + fPoint2)
637
638                camera.SetPosition((fPoint0 - rPoint0) + pPoint0,
639                                   (fPoint1 - rPoint1) + pPoint1,
640                                   (fPoint2 - rPoint2) + pPoint2)
641
642            self._LastX = x
643            self._LastY = y
644
645            self.Render()
646
647    def Zoom(self, event):
648        """Zooms the scene (camera).
649        """
650        if self._CurrentRenderer:
651            x = event.GetX()
652            y = event.GetY()
653
654            renderer = self._CurrentRenderer
655            camera = self._CurrentCamera
656
657            zoomFactor = math.pow(1.02,(0.5*(self._LastY - y)))
658            self._CurrentZoom = self._CurrentZoom * zoomFactor
659
660            if camera.GetParallelProjection():
661                parallelScale = camera.GetParallelScale()/zoomFactor
662                camera.SetParallelScale(parallelScale)
663            else:
664                camera.Dolly(zoomFactor)
665                renderer.ResetCameraClippingRange()
666
667            self._LastX = x
668            self._LastY = y
669
670            self.Render()
671
672    def Reset(self, event=None):
673        """Resets the camera.
674        """
675        if self._CurrentRenderer:
676            self._CurrentRenderer.ResetCamera()
677
678        self.Render()
679
680    def Wireframe(self):
681        """Sets the current actor representation as wireframe.
682        """
683        actors = self._CurrentRenderer.GetActors()
684        numActors = actors.GetNumberOfItems()
685        actors.InitTraversal()
686        for i in range(0,numActors):
687            actor = actors.GetNextItem()
688            actor.GetProperty().SetRepresentationToWireframe()
689
690        self.Render()
691
692    def Surface(self):
693        """Sets the current actor representation as surface.
694        """
695        actors = self._CurrentRenderer.GetActors()
696        numActors = actors.GetNumberOfItems()
697        actors.InitTraversal()
698        for i in range(0,numActors):
699            actor = actors.GetNextItem()
700            actor.GetProperty().SetRepresentationToSurface()
701
702        self.Render()
703
704    def PickActor(self, event):
705        """Picks an actor.
706        """
707        if self._CurrentRenderer:
708            x = event.GetX()
709            y = event.GetY()
710
711            renderer = self._CurrentRenderer
712            picker = self._Picker
713
714            windowX, windowY = self._RenderWindow.GetSize()
715            picker.Pick(x,(windowY - y - 1),0.0,renderer)
716            actor = picker.GetActor()
717
718            if (self._PickedActor != None and
719                self._PrePickedProperty != None):
720                self._PickedActor.SetProperty(self._PrePickedProperty)
721                # release hold of the property
722                self._PrePickedProperty.UnRegister(self._PrePickedProperty)
723                self._PrePickedProperty = None
724
725            if (actor != None):
726                self._PickedActor = actor
727                self._PrePickedProperty = self._PickedActor.GetProperty()
728                # hold onto the property
729                self._PrePickedProperty.Register(self._PrePickedProperty)
730                self._PickedActor.SetProperty(self._PickedProperty)
731
732            self.Render()
733
734
735#----------------------------------------------------------------------------
736def wxVTKRenderWindowConeExample():
737    """Like it says, just a simple example.
738    """
739    # every wx app needs an app
740    app = wx.App(False)
741
742    # create the widget
743    frame = wx.Frame(None, -1, "wxVTKRenderWindow", size=(400,400))
744    widget = wxVTKRenderWindow(frame, -1)
745
746    ren = vtk.vtkRenderer()
747    widget.GetRenderWindow().AddRenderer(ren)
748
749    cone = vtk.vtkConeSource()
750    cone.SetResolution(8)
751
752    coneMapper = vtk.vtkPolyDataMapper()
753    coneMapper.SetInputConnection(cone.GetOutputPort())
754
755    coneActor = vtk.vtkActor()
756    coneActor.SetMapper(coneMapper)
757
758    ren.AddActor(coneActor)
759
760    # show the window
761
762    frame.Show()
763
764    app.MainLoop()
765
766if __name__ == "__main__":
767    wxVTKRenderWindowConeExample()
768