Double Buffered wxVTKRenderWindowInteractor
Updated about 1 year ago (29 Jun 2009 at 04:50 PM)
recent activity
| In Brief | The version of wxVTKRenderWindowInteractor that ships with VTK (5.2 and earlier) does not have double buffering enabled for non-stereo rendering sometimes resulting in horrible flicker. This improved version, that I stole from Mayavi, has wx.glcanvas.WX_GL_DOUBLEBUFFER enabled thus fixing the flicker issues.... more |
| Language | Python |
# 's
1"""
2
3A VTK RenderWindowInteractor widget for wxPython.
4
5Find wxPython info at http://wxPython.org
6
7Created by Prabhu Ramachandran, April 2002
8Based on wxVTKRenderWindow.py
9
10Fixes and updates by Charl P. Botha 2003-2008
11
12Updated to new wx namespace and some cleaning up by Andrea Gavana,
13December 2006
14
15Source: http://svn.enthought.com/enthought/browser/Mayavi/trunk/enthought/tvtk/pyface/ui/wx
16"""
17
18"""
19Please see the example at the end of this file.
20
21----------------------------------------
22Creation:
23
24 wxVTKRenderWindowInteractor(parent, ID, stereo=0, [wx keywords]):
25
26 You should create a wx.PySimpleApp() or some other wx**App before
27 creating the window.
28
29Behaviour:
30
31 Uses __getattr__ to make the wxVTKRenderWindowInteractor behave just
32 like a vtkGenericRenderWindowInteractor.
33
34----------------------------------------
35
36"""
37
38# import usual libraries
39import math, os, sys
40import wx
41import vtk
42
43# wxPython 2.4.0.4 and newer prefers the use of True and False, standard
44# booleans in Python 2.2 but not earlier. Here we define these values if
45# they don't exist so that we can use True and False in the rest of the
46# code. At the time of this writing, that happens exactly ONCE in
47# CreateTimer()
48try:
49 True
50except NameError:
51 True = 1
52 False = 0
53
54# a few configuration items, see what works best on your system
55
56# Use GLCanvas as base class instead of wx.Window.
57# This is sometimes necessary under wxGTK or the image is blank.
58# (in wxWindows 2.3.1 and earlier, the GLCanvas had scroll bars)
59baseClass = wx.Window
60if wx.Platform == "__WXGTK__":
61 import wx.glcanvas
62 baseClass = wx.glcanvas.GLCanvas
63
64# Keep capturing mouse after mouse is dragged out of window
65# (in wxGTK 2.3.2 there is a bug that keeps this from working,
66# but it is only relevant in wxGTK if there are multiple windows)
67_useCapture = (wx.Platform == "__WXMSW__")
68
69# end of configuration items
70
71
72class EventTimer(wx.Timer):
73 """Simple wx.Timer class.
74 """
75
76 def __init__(self, iren):
77 """Default class constructor.
78 @param iren: current render window
79 """
80 wx.Timer.__init__(self)
81 self.iren = iren
82
83
84 def Notify(self):
85 """ The timer has expired.
86 """
87 self.iren.TimerEvent()
88
89
90class wxVTKRenderWindowInteractor(baseClass):
91 """
92 A wxRenderWindow for wxPython.
93 Use GetRenderWindow() to get the vtkRenderWindow.
94 Create with the keyword stereo=1 in order to
95 generate a stereo-capable window.
96 """
97
98 # class variable that can also be used to request instances that use
99 # stereo; this is overridden by the stereo=1/0 parameter. If you set
100 # it to True, the NEXT instantiated object will attempt to allocate a
101 # stereo visual. E.g.:
102 # wxVTKRenderWindowInteractor.USE_STEREO = True
103 # myRWI = wxVTKRenderWindowInteractor(parent, -1)
104 USE_STEREO = False
105
106 def __init__(self, parent, ID, *args, **kw):
107 """Default class constructor.
108
109 Parameters
110 ----------
111 parent
112 parent window
113 ID
114 window identifier
115 kw
116 wxPython keywords (position, size, style) plus the 'stereo' keyword
117 """
118 # private attributes
119 self.__RenderWhenDisabled = 0
120
121 # First do special handling of some keywords:
122 # stereo, position, size, style
123
124 stereo = 0
125
126 if kw.has_key('stereo'):
127 if kw['stereo']:
128 stereo = 1
129 del kw['stereo']
130
131 elif self.USE_STEREO:
132 stereo = 1
133
134 position, size = wx.DefaultPosition, wx.DefaultSize
135
136 if kw.has_key('position'):
137 position = kw['position']
138 del kw['position']
139
140 if kw.has_key('size'):
141 size = kw['size']
142 del kw['size']
143
144 # wx.WANTS_CHARS says to give us e.g. TAB
145 # wx.NO_FULL_REPAINT_ON_RESIZE cuts down resize flicker under GTK
146 style = wx.WANTS_CHARS | wx.NO_FULL_REPAINT_ON_RESIZE
147
148 if kw.has_key('style'):
149 style = style | kw['style']
150 del kw['style']
151
152 # the enclosing frame must be shown under GTK or the windows
153 # don't connect together properly
154 if wx.Platform != '__WXMSW__':
155 l = []
156 p = parent
157 while p: # make a list of all parents
158 l.append(p)
159 p = p.GetParent()
160 l.reverse() # sort list into descending order
161 for p in l:
162 p.Show(1)
163
164 if baseClass.__name__ == 'GLCanvas':
165 # code added by cpbotha to enable stereo and double
166 # buffering correctly where the user requests this; remember
167 # that the glXContext in this case is NOT allocated by VTK,
168 # but by WX, hence all of this.
169
170 # Initialize GLCanvas with correct attriblist
171 attribList = [wx.glcanvas.WX_GL_RGBA,
172 wx.glcanvas.WX_GL_MIN_RED, 1,
173 wx.glcanvas.WX_GL_MIN_GREEN, 1,
174 wx.glcanvas.WX_GL_MIN_BLUE, 1,
175 wx.glcanvas.WX_GL_DEPTH_SIZE, 16,
176 wx.glcanvas.WX_GL_DOUBLEBUFFER]
177 if stereo:
178 attribList.append(wx.glcanvas.WX_GL_STEREO)
179
180 try:
181 baseClass.__init__(self, parent, ID, position, size, style,
182 attribList=attribList)
183 except wx.PyAssertionError:
184 # visual couldn't be allocated, so we go back to default
185 baseClass.__init__(self, parent, ID, position, size, style)
186 if stereo:
187 # and make sure everyone knows that the stereo
188 # visual wasn't set.
189 stereo = 0
190
191 else:
192 baseClass.__init__(self, parent, ID, position, size, style)
193
194 # create the RenderWindow and initialize it
195 self._Iren = vtk.vtkGenericRenderWindowInteractor()
196 self._Iren.SetRenderWindow( vtk.vtkRenderWindow() )
197 self._Iren.AddObserver('CreateTimerEvent', self.CreateTimer)
198 self._Iren.AddObserver('DestroyTimerEvent', self.DestroyTimer)
199 self._Iren.GetRenderWindow().AddObserver('CursorChangedEvent',
200 self.CursorChangedEvent)
201
202 try:
203 self._Iren.GetRenderWindow().SetSize(size.width, size.height)
204 except AttributeError:
205 self._Iren.GetRenderWindow().SetSize(size[0], size[1])
206
207 if stereo:
208 self._Iren.GetRenderWindow().StereoCapableWindowOn()
209 self._Iren.GetRenderWindow().SetStereoTypeToCrystalEyes()
210
211 self.__handle = None
212
213 self.BindEvents()
214
215 # with this, we can make sure that the reparenting logic in
216 # Render() isn't called before the first OnPaint() has
217 # successfully been run (and set up the VTK/WX display links)
218 self.__has_painted = False
219
220 # set when we have captured the mouse.
221 self._own_mouse = False
222 # used to store WHICH mouse button led to mouse capture
223 self._mouse_capture_button = 0
224
225 # A mapping for cursor changes.
226 self._cursor_map = {0: wx.CURSOR_ARROW, # VTK_CURSOR_DEFAULT
227 1: wx.CURSOR_ARROW, # VTK_CURSOR_ARROW
228 2: wx.CURSOR_SIZENESW, # VTK_CURSOR_SIZENE
229 3: wx.CURSOR_SIZENWSE, # VTK_CURSOR_SIZENWSE
230 4: wx.CURSOR_SIZENESW, # VTK_CURSOR_SIZESW
231 5: wx.CURSOR_SIZENWSE, # VTK_CURSOR_SIZESE
232 6: wx.CURSOR_SIZENS, # VTK_CURSOR_SIZENS
233 7: wx.CURSOR_SIZEWE, # VTK_CURSOR_SIZEWE
234 8: wx.CURSOR_SIZING, # VTK_CURSOR_SIZEALL
235 9: wx.CURSOR_HAND, # VTK_CURSOR_HAND
236 10: wx.CURSOR_CROSS, # VTK_CURSOR_CROSSHAIR
237 }
238
239 def BindEvents(self):
240 """Binds all the necessary events for navigation, sizing,
241 drawing.
242 """
243 # refresh window by doing a Render
244 self.Bind(wx.EVT_PAINT, self.OnPaint)
245 # turn off background erase to reduce flicker
246 self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None)
247
248 # Bind the events to the event converters
249 self.Bind(wx.EVT_RIGHT_DOWN, self.OnButtonDown)
250 self.Bind(wx.EVT_LEFT_DOWN, self.OnButtonDown)
251 self.Bind(wx.EVT_MIDDLE_DOWN, self.OnButtonDown)
252 self.Bind(wx.EVT_RIGHT_UP, self.OnButtonUp)
253 self.Bind(wx.EVT_LEFT_UP, self.OnButtonUp)
254 self.Bind(wx.EVT_MIDDLE_UP, self.OnButtonUp)
255 self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
256 self.Bind(wx.EVT_MOTION, self.OnMotion)
257
258 # Bind the double clicks as well.
259 self.Bind(wx.EVT_LEFT_DCLICK, self.OnButtonDown)
260 self.Bind(wx.EVT_MIDDLE_DCLICK, self.OnButtonDown)
261 self.Bind(wx.EVT_RIGHT_DCLICK, self.OnButtonDown)
262
263 self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter)
264 self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
265
266 # If we use EVT_KEY_DOWN instead of EVT_CHAR, capital versions
267 # of all characters are always returned. EVT_CHAR also performs
268 # other necessary keyboard-dependent translations.
269 self.Bind(wx.EVT_CHAR, self.OnKeyDown)
270 self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
271
272 self.Bind(wx.EVT_SIZE, self.OnSize)
273
274 # the wx 2.8.7.1 documentation states that you HAVE to handle
275 # this event if you make use of CaptureMouse, which we do.
276 if _useCapture and hasattr(wx, 'EVT_MOUSE_CAPTURE_LOST'):
277 self.Bind(wx.EVT_MOUSE_CAPTURE_LOST,
278 self.OnMouseCaptureLost)
279
280
281 def __getattr__(self, attr):
282 """Makes the object behave like a
283 vtkGenericRenderWindowInteractor.
284 """
285 if attr == '__vtk__':
286 return lambda t=self._Iren: t
287 elif hasattr(self._Iren, attr):
288 return getattr(self._Iren, attr)
289 else:
290 raise AttributeError, self.__class__.__name__ + \
291 " has no attribute named " + attr
292
293 def CreateTimer(self, obj, evt):
294 """ Creates a timer.
295 """
296 self._timer = EventTimer(self)
297 self._timer.Start(10, True)
298
299 def DestroyTimer(self, obj, evt):
300 """The timer is a one shot timer so will expire automatically.
301 """
302 return 1
303
304 def _CursorChangedEvent(self, obj, evt):
305 """Change the wx cursor if the renderwindow's cursor was
306 changed.
307 """
308 cur = self._cursor_map[obj.GetCurrentCursor()]
309 c = wx.StockCursor(cur)
310 self.SetCursor(c)
311
312 def CursorChangedEvent(self, obj, evt):
313 """Called when the CursorChangedEvent fires on the render
314 window."""
315 # This indirection is needed since when the event fires, the
316 # current cursor is not yet set so we defer this by which time
317 # the current cursor should have been set.
318 wx.CallAfter(self._CursorChangedEvent, obj, evt)
319
320 def HideCursor(self):
321 """Hides the cursor."""
322 c = wx.StockCursor(wx.CURSOR_BLANK)
323 self.SetCursor(c)
324
325 def ShowCursor(self):
326 """Shows the cursor."""
327 rw = self._Iren.GetRenderWindow()
328 cur = self._cursor_map[rw.GetCurrentCursor()]
329 c = wx.StockCursor(cur)
330 self.SetCursor(c)
331
332 def GetDisplayId(self):
333 """Function to get X11 Display ID from WX and return it in a format
334 that can be used by VTK Python.
335
336 We query the X11 Display with a new call that was added in wxPython
337 2.6.0.1. The call returns a SWIG object which we can query for the
338 address and subsequently turn into an old-style SWIG-mangled string
339 representation to pass to VTK.
340 """
341 d = None
342
343 try:
344 d = wx.GetXDisplay()
345
346 except NameError:
347 # wx.GetXDisplay was added by Robin Dunn in wxPython 2.6.0.1
348 # if it's not available, we can't pass it. In general,
349 # things will still work; on some setups, it'll break.
350 pass
351
352 else:
353 # wx returns None on platforms where wx.GetXDisplay is not relevant
354 if d:
355 d = hex(d)
356 # On wxPython-2.6.3.2 and above there is no leading '0x'.
357 if not d.startswith('0x'):
358 d = '0x' + d
359
360 # we now have 0xdeadbeef
361 # VTK wants it as: _deadbeef_void_p (pre-SWIG-1.3 style)
362 d = '_%s_%s' % (d[2:], 'void_p')
363
364 return d
365
366 def OnMouseCaptureLost(self, event):
367 """This is signalled when we lose mouse capture due to an
368 external event, such as when a dialog box is shown. See the
369 wx documentation.
370 """
371
372 # the documentation seems to imply that by this time we've
373 # already lost capture. I have to assume that we don't need
374 # to call ReleaseMouse ourselves.
375 if _useCapture and self._own_mouse:
376 self._own_mouse = False
377
378 def OnPaint(self,event):
379 """Handles the wx.EVT_PAINT event for
380 wxVTKRenderWindowInteractor.
381 """
382
383 # wx should continue event processing after this handler.
384 # We call this BEFORE Render(), so that if Render() raises
385 # an exception, wx doesn't re-call OnPaint repeatedly.
386 event.Skip()
387
388 dc = wx.PaintDC(self)
389
390 # Check that a renderwindow exists, if not do nothing.
391 rw = self._Iren.GetRenderWindow()
392 if rw is None:
393 return
394
395 # make sure the RenderWindow is sized correctly
396 rw.SetSize(self.GetSizeTuple())
397
398 # Tell the RenderWindow to render inside the wx.Window.
399 if not self.__handle:
400
401 # on relevant platforms, set the X11 Display ID
402 d = self.GetDisplayId()
403 if d:
404 self._Iren.GetRenderWindow().SetDisplayId(d)
405
406 # store the handle
407 self.__handle = self.GetHandle()
408 # and give it to VTK
409 self._Iren.GetRenderWindow().SetWindowInfo(str(self.__handle))
410
411 # now that we've painted once, the Render() reparenting logic
412 # is safe
413 self.__has_painted = True
414
415 self.Render()
416
417 def OnSize(self,event):
418 """Handles the wx.EVT_SIZE event for
419 wxVTKRenderWindowInteractor.
420 """
421
422 # event processing should continue (we call this before the
423 # Render(), in case it raises an exception)
424 event.Skip()
425
426 try:
427 width, height = event.GetSize()
428 except:
429 width = event.GetSize().width
430 height = event.GetSize().height
431 self._Iren.SetSize(width, height)
432 self._Iren.ConfigureEvent()
433
434 # this will check for __handle
435 self.Render()
436
437 def OnMotion(self,event):
438 """Handles the wx.EVT_MOTION event for
439 wxVTKRenderWindowInteractor.
440 """
441
442 # event processing should continue
443 # we call this early in case any of the VTK code raises an
444 # exception.
445 event.Skip()
446
447 self._Iren.SetEventInformationFlipY(event.GetX(), event.GetY(),
448 event.ControlDown(),
449 event.ShiftDown(),
450 chr(0), 0, None)
451 self._Iren.MouseMoveEvent()
452
453 def OnEnter(self,event):
454 """Handles the wx.EVT_ENTER_WINDOW event for
455 wxVTKRenderWindowInteractor.
456 """
457
458 # event processing should continue
459 event.Skip()
460
461 self._Iren.SetEventInformationFlipY(event.GetX(), event.GetY(),
462 event.ControlDown(),
463 event.ShiftDown(),
464 chr(0), 0, None)
465 self._Iren.EnterEvent()
466
467
468 def OnLeave(self,event):
469 """Handles the wx.EVT_LEAVE_WINDOW event for
470 wxVTKRenderWindowInteractor.
471 """
472
473 # event processing should continue
474 event.Skip()
475
476 self._Iren.SetEventInformationFlipY(event.GetX(), event.GetY(),
477 event.ControlDown(),
478 event.ShiftDown(),
479 chr(0), 0, None)
480 self._Iren.LeaveEvent()
481
482
483 def OnButtonDown(self,event):
484 """Handles the wx.EVT_LEFT/RIGHT/MIDDLE_DOWN events for
485 wxVTKRenderWindowInteractor.
486 """
487
488 # allow wx event processing to continue
489 # on wxPython 2.6.0.1, omitting this will cause problems with
490 # the initial focus, resulting in the wxVTKRWI ignoring keypresses
491 # until we focus elsewhere and then refocus the wxVTKRWI frame
492 # we do it this early in case any of the following VTK code
493 # raises an exception.
494 event.Skip()
495
496 ctrl, shift = event.ControlDown(), event.ShiftDown()
497 self._Iren.SetEventInformationFlipY(event.GetX(), event.GetY(),
498 ctrl, shift, chr(0), 0, None)
499
500 button = 0
501 if event.RightIsDown():
502 self._Iren.RightButtonPressEvent()
503 button = 'Right'
504 elif event.LeftIsDown():
505 self._Iren.LeftButtonPressEvent()
506 button = 'Left'
507 elif event.MiddleIsDown():
508 self._Iren.MiddleButtonPressEvent()
509 button = 'Middle'
510
511 # save the button and capture mouse until the button is released
512 # we only capture the mouse if it hasn't already been captured
513 if _useCapture and not self._own_mouse:
514 self._own_mouse = True
515 self._mouse_capture_button = button
516 self.CaptureMouse()
517
518
519 def OnButtonUp(self,event):
520 """Handles the wx.EVT_LEFT/RIGHT/MIDDLE_UP events for
521 wxVTKRenderWindowInteractor.
522 """
523
524 # event processing should continue
525 event.Skip()
526
527 button = 0
528 if event.RightUp():
529 button = 'Right'
530 elif event.LeftUp():
531 button = 'Left'
532 elif event.MiddleUp():
533 button = 'Middle'
534
535 # if the same button is released that captured the mouse, and
536 # we have the mouse, release it.
537 # (we need to get rid of this as soon as possible; if we don't
538 # and one of the event handlers raises an exception, mouse
539 # is never released.)
540 if _useCapture and self._own_mouse and \
541 button==self._mouse_capture_button:
542 self.ReleaseMouse()
543 self._own_mouse = False
544
545 ctrl, shift = event.ControlDown(), event.ShiftDown()
546 self._Iren.SetEventInformationFlipY(event.GetX(), event.GetY(),
547 ctrl, shift, chr(0), 0, None)
548
549 if button == 'Right':
550 self._Iren.RightButtonReleaseEvent()
551 elif button == 'Left':
552 self._Iren.LeftButtonReleaseEvent()
553 elif button == 'Middle':
554 self._Iren.MiddleButtonReleaseEvent()
555
556
557 def OnMouseWheel(self,event):
558 """Handles the wx.EVT_MOUSEWHEEL event for
559 wxVTKRenderWindowInteractor.
560 """
561
562 # event processing should continue
563 event.Skip()
564
565 ctrl, shift = event.ControlDown(), event.ShiftDown()
566 self._Iren.SetEventInformationFlipY(event.GetX(), event.GetY(),
567 ctrl, shift, chr(0), 0, None)
568 if event.GetWheelRotation() > 0:
569 self._Iren.MouseWheelForwardEvent()
570 else:
571 self._Iren.MouseWheelBackwardEvent()
572
573
574 def OnKeyDown(self,event):
575 """Handles the wx.EVT_KEY_DOWN event for
576 wxVTKRenderWindowInteractor.
577 """
578
579 # event processing should continue
580 event.Skip()
581
582 ctrl, shift = event.ControlDown(), event.ShiftDown()
583 keycode, keysym = event.GetKeyCode(), None
584 key = chr(0)
585 if keycode < 256:
586 key = chr(keycode)
587
588 # wxPython 2.6.0.1 does not return a valid event.Get{X,Y}()
589 # for this event, so we use the cached position.
590 (x,y)= self._Iren.GetEventPosition()
591 self._Iren.SetEventInformation(x, y,
592 ctrl, shift, key, 0,
593 keysym)
594
595 self._Iren.KeyPressEvent()
596 self._Iren.CharEvent()
597
598
599 def OnKeyUp(self,event):
600 """Handles the wx.EVT_KEY_UP event for
601 wxVTKRenderWindowInteractor.
602 """
603
604 # event processing should continue
605 event.Skip()
606
607 ctrl, shift = event.ControlDown(), event.ShiftDown()
608 keycode, keysym = event.GetKeyCode(), None
609 key = chr(0)
610 if keycode < 256:
611 key = chr(keycode)
612
613 self._Iren.SetEventInformationFlipY(event.GetX(), event.GetY(),
614 ctrl, shift, key, 0,
615 keysym)
616 self._Iren.KeyReleaseEvent()
617
618
619 def GetRenderWindow(self):
620 """Returns the render window (vtkRenderWindow).
621 """
622 return self._Iren.GetRenderWindow()
623
624 def Render(self):
625 """Actually renders the VTK scene on screen.
626 """
627 RenderAllowed = 1
628
629 if not self.__RenderWhenDisabled:
630 # the user doesn't want us to render when the toplevel frame
631 # is disabled - first find the top level parent
632 topParent = wx.GetTopLevelParent(self)
633 if topParent:
634 # if it exists, check whether it's enabled
635 # if it's not enabeld, RenderAllowed will be false
636 RenderAllowed = topParent.IsEnabled()
637
638 if RenderAllowed:
639 if self.__handle and self.__handle == self.GetHandle():
640 self._Iren.GetRenderWindow().Render()
641
642 elif self.GetHandle() and self.__has_painted:
643 # this means the user has reparented us; let's adapt to the
644 # new situation by doing the WindowRemap dance
645 self._Iren.GetRenderWindow().SetNextWindowInfo(
646 str(self.GetHandle()))
647
648 # make sure the DisplayId is also set correctly
649 d = self.GetDisplayId()
650 if d:
651 self._Iren.GetRenderWindow().SetDisplayId(d)
652
653 # do the actual remap with the new parent information
654 self._Iren.GetRenderWindow().WindowRemap()
655
656 # store the new situation
657 self.__handle = self.GetHandle()
658 self._Iren.GetRenderWindow().Render()
659
660 def SetRenderWhenDisabled(self, newValue):
661 """Change value of __RenderWhenDisabled ivar.
662
663 If __RenderWhenDisabled is false (the default), this widget will not
664 call Render() on the RenderWindow if the top level frame (i.e. the
665 containing frame) has been disabled.
666
667 This prevents recursive rendering during wx.SafeYield() calls.
668 wx.SafeYield() can be called during the ProgressMethod() callback of
669 a VTK object to have progress bars and other GUI elements updated -
670 it does this by disabling all windows (disallowing user-input to
671 prevent re-entrancy of code) and then handling all outstanding
672 GUI events.
673
674 However, this often triggers an OnPaint() method for wxVTKRWIs,
675 resulting in a Render(), resulting in Update() being called whilst
676 still in progress.
677 """
678 self.__RenderWhenDisabled = bool(newValue)
679
680
681#--------------------------------------------------------------------
682def wxVTKRenderWindowInteractorConeExample():
683 """Like it says, just a simple example
684 """
685 # every wx app needs an app
686 app = wx.PySimpleApp()
687
688 # create the top-level frame, sizer and wxVTKRWI
689 frame = wx.Frame(None, -1, "wxVTKRenderWindowInteractor", size=(400,400))
690 widget = wxVTKRenderWindowInteractor(frame, -1)
691 sizer = wx.BoxSizer(wx.VERTICAL)
692 sizer.Add(widget, 1, wx.EXPAND)
693 frame.SetSizer(sizer)
694 frame.Layout()
695
696 # It would be more correct (API-wise) to call widget.Initialize() and
697 # widget.Start() here, but Initialize() calls RenderWindow.Render().
698 # That Render() call will get through before we can setup the
699 # RenderWindow() to render via the wxWidgets-created context; this
700 # causes flashing on some platforms and downright breaks things on
701 # other platforms. Instead, we call widget.Enable(). This means
702 # that the RWI::Initialized ivar is not set, but in THIS SPECIFIC CASE,
703 # that doesn't matter.
704 widget.Enable(1)
705
706 widget.AddObserver("ExitEvent", lambda o,e,f=frame: f.Close())
707
708 ren = vtk.vtkRenderer()
709 widget.GetRenderWindow().AddRenderer(ren)
710
711 cone = vtk.vtkConeSource()
712 cone.SetResolution(8)
713
714 coneMapper = vtk.vtkPolyDataMapper()
715 coneMapper.SetInput(cone.GetOutput())
716
717 coneActor = vtk.vtkActor()
718 coneActor.SetMapper(coneMapper)
719
720 ren.AddActor(coneActor)
721
722 # show the window
723 frame.Show()
724
725 app.MainLoop()
726
727if __name__ == "__main__":
728 wxVTKRenderWindowInteractorConeExample()
The version of wxVTKRenderWindowInteractor that ships with VTK (5.2 and earlier) does not have double buffering enabled for non-stereo rendering sometimes resulting in horrible flicker. This improved version, that I stole from Mayavi, has wx.glcanvas.WX_GL_DOUBLEBUFFER enabled thus fixing the flicker issues.
There does not seem to be any way to pass custom attribList to wxVTKRenderWindowInteractor and so using this modified class is the only way to enable double buffering. Why isn't double buffering enabled by default on wxWidgets GLCanvas windows you ask? Who knows.
Add a Comment