wxVTKRenderWindowInteractor.py 23.2 KB
Newer Older
1
2
"""

3
A VTK RenderWindowInteractor widget for wxPython.
4
5
6
7
8

Find wxPython info at http://wxPython.org

Created by Prabhu Ramachandran, April 2002
Based on wxVTKRenderWindow.py
9

10
Fixes and updates by Charl P. Botha 2003-2007
11
12
13

Updated to new wx namespace and some cleaning up by Andrea Gavana,
December 2006
14
15
16
17
18
19
20
21
22
23
"""

"""
Please see the example at the end of this file.

----------------------------------------
Creation:

 wxVTKRenderWindowInteractor(parent, ID, stereo=0, [wx keywords]):
 
24
 You should create a wx.PySimpleApp() or some other wx**App before
25
26
27
28
29
30
31
32
33
34
35
36
37
 creating the window.

Behaviour:

 Uses __getattr__ to make the wxVTKRenderWindowInteractor behave just
 like a vtkGenericRenderWindowInteractor.

----------------------------------------

"""

# import usual libraries
import math, os, sys
38
import wx
39
40
import vtk

41
42
43
44
45
46
47
48
49
50
51
# wxPython 2.4.0.4 and newer prefers the use of True and False, standard
# booleans in Python 2.2 but not earlier.  Here we define these values if
# they don't exist so that we can use True and False in the rest of the
# code.  At the time of this writing, that happens exactly ONCE in
# CreateTimer()
try:
    True
except NameError:
    True = 1
    False = 0

52
53
# a few configuration items, see what works best on your system

54
# Use GLCanvas as base class instead of wx.Window.
55
56
# This is sometimes necessary under wxGTK or the image is blank.
# (in wxWindows 2.3.1 and earlier, the GLCanvas had scroll bars)
57
58
59
60
baseClass = wx.Window
if wx.Platform == "__WXGTK__":
    import wx.glcanvas
    baseClass = wx.glcanvas.GLCanvas
61

62
63
64
# Keep capturing mouse after mouse is dragged out of window
# (in wxGTK 2.3.2 there is a bug that keeps this from working,
# but it is only relevant in wxGTK if there are multiple windows)
65
_useCapture = (wx.Platform == "__WXMSW__")
66
67
68
69

# end of configuration items


70
71
72
class EventTimer(wx.Timer):
    """Simple wx.Timer class.
    """
73
74

    def __init__(self, iren):
75
76
77
78
        """Default class constructor.
        @param iren: current render window
        """
        wx.Timer.__init__(self)
79
        self.iren = iren
80
81

        
82
    def Notify(self):
83
84
        """ The timer has expired.
        """
85
86
        self.iren.TimerEvent()

87

88
89
90
91
92
93
94
class wxVTKRenderWindowInteractor(baseClass):
    """
    A wxRenderWindow for wxPython.
    Use GetRenderWindow() to get the vtkRenderWindow.
    Create with the keyword stereo=1 in order to
    generate a stereo-capable window.
    """
95
96
97
98
99
100
101
102
103

    # class variable that can also be used to request instances that use
    # stereo; this is overridden by the stereo=1/0 parameter.  If you set
    # it to True, the NEXT instantiated object will attempt to allocate a
    # stereo visual.  E.g.:
    # wxVTKRenderWindowInteractor.USE_STEREO = True
    # myRWI = wxVTKRenderWindowInteractor(parent, -1)
    USE_STEREO = False
    
104
    def __init__(self, parent, ID, *args, **kw):
105
106
107
108
109
110
        """Default class constructor.
        @param parent: parent window
        @param ID: window id
        @param **kw: wxPython keywords (position, size, style) plus the
        'stereo' keyword
        """
111
        # private attributes
Ken Martin's avatar
Ken Martin committed
112
113
        self.__RenderWhenDisabled = 0

114
        # First do special handling of some keywords:
115
        # stereo, position, size, style
116
117
118
119
120
121
122
123
        
        stereo = 0
        
        if kw.has_key('stereo'):
            if kw['stereo']:
                stereo = 1
            del kw['stereo']

124
125
126
        elif self.USE_STEREO:
            stereo = 1

127
        position, size = wx.DefaultPosition, wx.DefaultSize
128
129
130
131
132
133
134
135
136

        if kw.has_key('position'):
            position = kw['position']
            del kw['position']

        if kw.has_key('size'):
            size = kw['size']
            del kw['size']
        
137
138
139
        # wx.WANTS_CHARS says to give us e.g. TAB
        # wx.NO_FULL_REPAINT_ON_RESIZE cuts down resize flicker under GTK
        style = wx.WANTS_CHARS | wx.NO_FULL_REPAINT_ON_RESIZE
140
141
142
143
144
145
146

        if kw.has_key('style'):
            style = style | kw['style']
            del kw['style']

        # the enclosing frame must be shown under GTK or the windows
        #  don't connect together properly
147
        if wx.Platform != '__WXMSW__':
148
149
150
151
152
153
154
155
            l = []
            p = parent
            while p: # make a list of all parents
                l.append(p)
                p = p.GetParent()
            l.reverse() # sort list into descending order
            for p in l:
                p.Show(1)
156

157
158
159
        # code added by cpbotha to enable stereo correctly where the user
        # requests this; remember that the glXContext in this case is NOT
        # allocated by VTK, but by WX, hence all of this.
160
        if stereo and baseClass.__name__ == 'GLCanvas':
161
            # initialize GLCanvas with correct attriblist for stereo
162
163
164
165
166
167
168
            attribList = [wx.glcanvas.WX_GL_RGBA, 
                          wx.glcanvas.WX_GL_MIN_RED, 1,
                          wx.glcanvas.WX_GL_MIN_GREEN, 1,
                          wx.glcanvas.WX_GL_MIN_BLUE, 1, 
                          wx.glcanvas.WX_GL_DEPTH_SIZE, 1,
                          wx.glcanvas.WX_GL_DOUBLEBUFFER,
                          wx.glcanvas.WX_GL_STEREO]
169
170
171
172
            try:
                baseClass.__init__(self, parent, ID, position, size, style, 
                                   attribList=attribList)
                
173
            except wx.PyAssertionError:
174
175
176
177
178
179
180
                # stereo visual couldn't be allocated, so we go back to default
                baseClass.__init__(self, parent, ID, position, size, style)
                # and make sure everyone knows about it
                stereo = 0

        else:
            baseClass.__init__(self, parent, ID, position, size, style)
181
182

        # create the RenderWindow and initialize it
183
184
185
186
        self._Iren = vtk.vtkGenericRenderWindowInteractor()
        self._Iren.SetRenderWindow( vtk.vtkRenderWindow() )
        self._Iren.AddObserver('CreateTimerEvent', self.CreateTimer)
        self._Iren.AddObserver('DestroyTimerEvent', self.DestroyTimer)
187
188
        self._Iren.GetRenderWindow().AddObserver('CursorChangedEvent',
                                                 self.CursorChangedEvent)
189

190
        try:
191
            self._Iren.GetRenderWindow().SetSize(size.width, size.height)
192
        except AttributeError:
193
            self._Iren.GetRenderWindow().SetSize(size[0], size[1])
194
            
195
        if stereo:
196
197
            self._Iren.GetRenderWindow().StereoCapableWindowOn()
            self._Iren.GetRenderWindow().SetStereoTypeToCrystalEyes()
198

199
        self.__handle = None
200
201
202
203
        self._ActiveButton = 0

        self.BindEvents()

204
205
206
207
208
        # with this, we can make sure that the reparenting logic in
        # Render() isn't called before the first OnPaint() has
        # successfully been run (and set up the VTK/WX display links)
        self.__has_painted = False

209
210
        # set when we have captured the mouse.
        self._own_mouse = False
211
212
213
214
215
216
217
218
219
220
221
222
223
224

        # A mapping for cursor changes.
        self._cursor_map = {0: wx.CURSOR_ARROW, # VTK_CURSOR_DEFAULT
                            1: wx.CURSOR_ARROW, # VTK_CURSOR_ARROW
                            2: wx.CURSOR_SIZENESW, # VTK_CURSOR_SIZENE
                            3: wx.CURSOR_SIZENWSE, # VTK_CURSOR_SIZENWSE
                            4: wx.CURSOR_SIZENESW, # VTK_CURSOR_SIZESW
                            5: wx.CURSOR_SIZENWSE, # VTK_CURSOR_SIZESE
                            6: wx.CURSOR_SIZENS, # VTK_CURSOR_SIZENS
                            7: wx.CURSOR_SIZEWE, # VTK_CURSOR_SIZEWE
                            8: wx.CURSOR_SIZING, # VTK_CURSOR_SIZEALL
                            9: wx.CURSOR_HAND, # VTK_CURSOR_HAND
                            10: wx.CURSOR_CROSS, # VTK_CURSOR_CROSSHAIR
                           }
225
        
226
    def BindEvents(self):
227
228
229
        """Binds all the necessary events for navigation, sizing,
        drawing.
        """        
230
        # refresh window by doing a Render
231
        self.Bind(wx.EVT_PAINT, self.OnPaint)
232
        # turn off background erase to reduce flicker
233
        self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None)
234
235
        
        # Bind the events to the event converters
236
237
238
239
240
241
242
243
244
245
246
        self.Bind(wx.EVT_RIGHT_DOWN, self.OnButtonDown)
        self.Bind(wx.EVT_LEFT_DOWN, self.OnButtonDown)
        self.Bind(wx.EVT_MIDDLE_DOWN, self.OnButtonDown)
        self.Bind(wx.EVT_RIGHT_UP, self.OnButtonUp)
        self.Bind(wx.EVT_LEFT_UP, self.OnButtonUp)
        self.Bind(wx.EVT_MIDDLE_UP, self.OnButtonUp)
        self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
        self.Bind(wx.EVT_MOTION, self.OnMotion)

        self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter)
        self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
247

248
249
250
        # If we use EVT_KEY_DOWN instead of EVT_CHAR, capital versions
        # of all characters are always returned.  EVT_CHAR also performs
        # other necessary keyboard-dependent translations.
251
252
        self.Bind(wx.EVT_CHAR, self.OnKeyDown)
        self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
253
        
254
255
        self.Bind(wx.EVT_SIZE, self.OnSize)

256
257
    def __getattr__(self, attr):        
        """Makes the object behave like a
258
259
        vtkGenericRenderWindowInteractor.
        """
260
261
262
263
264
265
266
267
268
        if attr == '__vtk__':
            return lambda t=self._Iren: t
        elif hasattr(self._Iren, attr):
            return getattr(self._Iren, attr)
        else:
            raise AttributeError, self.__class__.__name__ + \
                  " has no attribute named " + attr

    def CreateTimer(self, obj, evt):
269
270
        """ Creates a timer.
        """
271
272
        self._timer = EventTimer(self)
        self._timer.Start(10, True)
273
274

    def DestroyTimer(self, obj, evt):
275
276
        """The timer is a one shot timer so will expire automatically.
        """
277
        return 1
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
   
    def _CursorChangedEvent(self, obj, evt):
        """Change the wx cursor if the renderwindow's cursor was
        changed. 
        """
        cur = self._cursor_map[obj.GetCurrentCursor()]
        c = wx.StockCursor(cur)
        self.SetCursor(c)

    def CursorChangedEvent(self, obj, evt):
        """Called when the CursorChangedEvent fires on the render
        window."""
        # This indirection is needed since when the event fires, the
        # current cursor is not yet set so we defer this by which time
        # the current cursor should have been set.
        wx.CallAfter(self._CursorChangedEvent, obj, evt)

    def HideCursor(self):
        """Hides the cursor."""
        c = wx.StockCursor(wx.CURSOR_BLANK)
        self.SetCursor(c)

    def ShowCursor(self):
        """Shows the cursor."""
        rw = self._Iren.GetRenderWindow()
        cur = self._cursor_map[rw.GetCurrentCursor()]
        c = wx.StockCursor(cur)
        self.SetCursor(c)

307
308
309
310
311
312
313
314
315
316
317
318
    def GetDisplayId(self):
        """Function to get X11 Display ID from WX and return it in a format
        that can be used by VTK Python.

        We query the X11 Display with a new call that was added in wxPython
        2.6.0.1.  The call returns a SWIG object which we can query for the
        address and subsequently turn into an old-style SWIG-mangled string
        representation to pass to VTK.
        """
        d = None

        try:
319
            d = wx.GetXDisplay()
320
321
            
        except NameError:
322
            # wx.GetXDisplay was added by Robin Dunn in wxPython 2.6.0.1
323
324
325
326
327
            # if it's not available, we can't pass it.  In general, 
            # things will still work; on some setups, it'll break.
            pass
        
        else:
328
            # wx returns None on platforms where wx.GetXDisplay is not relevant
329
330
            if d:
                d = hex(d)
331
332
333
334
                # On wxPython-2.6.3.2 and above there is no leading '0x'.
                if not d.startswith('0x'):
                    d = '0x' + d
                
335
336
337
338
339
                # we now have 0xdeadbeef
                # VTK wants it as: _deadbeef_void_p (pre-SWIG-1.3 style)
                d = '_%s_%s' % (d[2:], 'void_p')

        return d
340
341

    def OnPaint(self,event):
342
343
344
        """Handles the wx.EVT_PAINT event for
        wxVTKRenderWindowInteractor.
        """
345
346
347
348
349
350

        # wx should continue event processing after this handler.
        # We call this BEFORE Render(), so that if Render() raises
        # an exception, wx doesn't re-call OnPaint repeatedly.
        event.Skip()
        
351
        dc = wx.PaintDC(self)
352
353
354
355

        # make sure the RenderWindow is sized correctly
        self._Iren.GetRenderWindow().SetSize(self.GetSizeTuple())
        
356
        # Tell the RenderWindow to render inside the wx.Window.
357
        if not self.__handle:
358
359
360
361
362
363
364

            # on relevant platforms, set the X11 Display ID
            d = self.GetDisplayId()
            if d:
                self._Iren.GetRenderWindow().SetDisplayId(d)

            # store the handle
365
            self.__handle = self.GetHandle()
366
            # and give it to VTK
367
            self._Iren.GetRenderWindow().SetWindowInfo(str(self.__handle))
368
369
370
371

            # now that we've painted once, the Render() reparenting logic
            # is safe
            self.__has_painted = True
372

373
374
375
        self.Render()

    def OnSize(self,event):
376
377
378
        """Handles the wx.EVT_SIZE event for
        wxVTKRenderWindowInteractor.
        """
379
380
381
382
383

        # event processing should continue (we call this before the
        # Render(), in case it raises an exception)
        event.Skip()

384
385
386
387
388
389
        try:
            width, height = event.GetSize()
        except:
            width = event.GetSize().width
            height = event.GetSize().height
        self._Iren.SetSize(width, height)
Ken Martin's avatar
Ken Martin committed
390
        self._Iren.ConfigureEvent()
391

392
        # this will check for __handle
Ken Martin's avatar
Ken Martin committed
393
        self.Render()
394
395

    def OnMotion(self, event):
396
397
398
        """Handles the wx.EVT_MOTION event for
        wxVTKRenderWindowInteractor.
        """
Charl Botha's avatar
   
Charl Botha committed
399
400
401
402
403
404
        
        # event processing should continue
        # we call this early in case any of the VTK code raises an
        # exception.
        event.Skip()
        
405
        self._Iren.SetEventInformationFlipY(event.GetX(), event.GetY(),
406
407
408
                                            event.ControlDown(),
                                            event.ShiftDown(),
                                            chr(0), 0, None)
409
410
        self._Iren.MouseMoveEvent()

411

412
    def OnEnter(self,event):
413
414
415
        """Handles the wx.EVT_ENTER_WINDOW event for
        wxVTKRenderWindowInteractor.
        """
Charl Botha's avatar
   
Charl Botha committed
416
417
418
419
        
        # event processing should continue
        event.Skip()
        
420
        self._Iren.SetEventInformationFlipY(event.GetX(), event.GetY(),
421
422
423
                                            event.ControlDown(), 
					    event.ShiftDown(), 
					    chr(0), 0, None)
424
        self._Iren.EnterEvent()
425

426
427
        
    def OnLeave(self,event):
428
429
430
        """Handles the wx.EVT_LEAVE_WINDOW event for
        wxVTKRenderWindowInteractor.
        """
Charl Botha's avatar
   
Charl Botha committed
431
432
433
434
        
        # event processing should continue
        event.Skip()

435
        self._Iren.SetEventInformationFlipY(event.GetX(), event.GetY(),
436
437
438
                                            event.ControlDown(), 
					    event.ShiftDown(), 
					    chr(0), 0, None)
439
440
441
        self._Iren.LeaveEvent()

        
442
    def OnButtonDown(self,event):
443
444
445
        """Handles the wx.EVT_LEFT/RIGHT/MIDDLE_DOWN events for
        wxVTKRenderWindowInteractor.
        """
Charl Botha's avatar
   
Charl Botha committed
446
447
448
449
450
451
452
453
454
        
        # allow wx event processing to continue
        # on wxPython 2.6.0.1, omitting this will cause problems with
        # the initial focus, resulting in the wxVTKRWI ignoring keypresses
        # until we focus elsewhere and then refocus the wxVTKRWI frame
        # we do it this early in case any of the following VTK code
        # raises an exception.
        event.Skip()
        
455
456
457
        ctrl, shift = event.ControlDown(), event.ShiftDown()
        self._Iren.SetEventInformationFlipY(event.GetX(), event.GetY(),
                                            ctrl, shift, chr(0), 0, None)
458
                                            
459
460
461
462
463
464
465
466
467
468
        self._ActiveButton = 0
        if event.RightDown():
            self._Iren.RightButtonPressEvent()
            self._ActiveButton = 'Right'
        elif event.LeftDown():
            self._Iren.LeftButtonPressEvent()
            self._ActiveButton = 'Left'
        elif event.MiddleDown():
            self._Iren.MiddleButtonPressEvent()
            self._ActiveButton = 'Middle'
469

470
        # save the button and capture mouse until the button is released
471
        if self._ActiveButton and _useCapture:
472
            self._own_mouse = True
473
474
            self.CaptureMouse()

475

476
    def OnButtonUp(self,event):
477
478
479
        """Handles the wx.EVT_LEFT/RIGHT/MIDDLE_UP events for
        wxVTKRenderWindowInteractor.
        """
480

Charl Botha's avatar
   
Charl Botha committed
481
482
483
        # event processing should continue
        event.Skip()

484
485
486
487
488
489
490
491
        # if the ActiveButton is released, then release mouse capture
        # (we need to get rid of this as soon as possible; if we don't
        #  and one of the event handlers raises an exception, mouse
        #  is never released.)
        if self._own_mouse and self._ActiveButton and _useCapture:
            self.ReleaseMouse()
            self._own_mouse = False
        
492
493
494
495
496
497
498
499
500
501
502
        ctrl, shift = event.ControlDown(), event.ShiftDown()
        self._Iren.SetEventInformationFlipY(event.GetX(), event.GetY(),
                                            ctrl, shift, chr(0), 0, None)

        if self._ActiveButton == 'Right':
            self._Iren.RightButtonReleaseEvent()
        elif self._ActiveButton == 'Left':
            self._Iren.LeftButtonReleaseEvent()
        elif self._ActiveButton == 'Middle':
            self._Iren.MiddleButtonReleaseEvent()

503
            
504
    def OnMouseWheel(self,event):
505
506
507
        """Handles the wx.EVT_MOUSEWHEEL event for
        wxVTKRenderWindowInteractor.
        """
Charl Botha's avatar
   
Charl Botha committed
508
509
510
511
        
        # event processing should continue
        event.Skip()
        
512
513
514
        ctrl, shift = event.ControlDown(), event.ShiftDown()
        self._Iren.SetEventInformationFlipY(event.GetX(), event.GetY(),
                                            ctrl, shift, chr(0), 0, None)
515
        if event.GetWheelRotation() > 0:
516
517
518
            self._Iren.MouseWheelForwardEvent()
        else:
            self._Iren.MouseWheelBackwardEvent()
519

520
        
521
    def OnKeyDown(self,event):
522
523
524
        """Handles the wx.EVT_KEY_DOWN event for
        wxVTKRenderWindowInteractor.
        """
Charl Botha's avatar
   
Charl Botha committed
525
526
527
528

        # event processing should continue
        event.Skip()

529
530
531
532
533
        ctrl, shift = event.ControlDown(), event.ShiftDown()
        keycode, keysym = event.GetKeyCode(), None
        key = chr(0)
        if keycode < 256:
            key = chr(keycode)
534

535
536
537
538
539
540
541
        # wxPython 2.6.0.1 does not return a valid event.Get{X,Y}()
        # for this event, so we use the cached position.
        (x,y)= self._Iren.GetEventPosition()
        self._Iren.SetEventInformation(x, y,
                                       ctrl, shift, key, 0,
                                       keysym)

542
543
544
        self._Iren.KeyPressEvent()
        self._Iren.CharEvent()

545
        
546
    def OnKeyUp(self,event):
547
548
549
        """Handles the wx.EVT_KEY_UP event for
        wxVTKRenderWindowInteractor.
        """
Charl Botha's avatar
   
Charl Botha committed
550
551
552
553
        
        # event processing should continue
        event.Skip()
        
554
555
556
557
558
559
560
561
562
563
564
        ctrl, shift = event.ControlDown(), event.ShiftDown()
        keycode, keysym = event.GetKeyCode(), None
        key = chr(0)
        if keycode < 256:
            key = chr(keycode)

        self._Iren.SetEventInformationFlipY(event.GetX(), event.GetY(),
                                            ctrl, shift, key, 0,
                                            keysym)
        self._Iren.KeyReleaseEvent()

565

566
    def GetRenderWindow(self):
567
568
        """Returns the render window (vtkRenderWindow).
        """
569
        return self._Iren.GetRenderWindow()
570
571

    def Render(self):
572
573
        """Actually renders the VTK scene on screen.
        """
Ken Martin's avatar
Ken Martin committed
574
575
576
577
578
        RenderAllowed = 1
        
        if not self.__RenderWhenDisabled:
            # the user doesn't want us to render when the toplevel frame
            # is disabled - first find the top level parent
579
            topParent = wx.GetTopLevelParent(self)
Ken Martin's avatar
Ken Martin committed
580
581
582
583
584
            if topParent:
                # if it exists, check whether it's enabled
                # if it's not enabeld, RenderAllowed will be false
                RenderAllowed = topParent.IsEnabled()
            
585
586
        if RenderAllowed:
            if self.__handle and self.__handle == self.GetHandle():
587
                self._Iren.GetRenderWindow().Render()
588

589
            elif self.GetHandle() and self.__has_painted:
590
591
                # this means the user has reparented us; let's adapt to the
                # new situation by doing the WindowRemap dance
592
593
594
595
596
597
598
599
600
                self._Iren.GetRenderWindow().SetNextWindowInfo(
                    str(self.GetHandle()))

                # make sure the DisplayId is also set correctly
                d = self.GetDisplayId()
                if d:
                    self._Iren.GetRenderWindow().SetDisplayId(d)
                
                # do the actual remap with the new parent information
601
                self._Iren.GetRenderWindow().WindowRemap()
602

603
604
                # store the new situation
                self.__handle = self.GetHandle()
605
                self._Iren.GetRenderWindow().Render()
606

Ken Martin's avatar
Ken Martin committed
607
608
609
610
611
612
613
    def SetRenderWhenDisabled(self, newValue):
        """Change value of __RenderWhenDisabled ivar.

        If __RenderWhenDisabled is false (the default), this widget will not
        call Render() on the RenderWindow if the top level frame (i.e. the
        containing frame) has been disabled.

614
615
        This prevents recursive rendering during wx.SafeYield() calls.
        wx.SafeYield() can be called during the ProgressMethod() callback of
Ken Martin's avatar
Ken Martin committed
616
617
618
619
620
621
622
623
624
625
626
        a VTK object to have progress bars and other GUI elements updated -
        it does this by disabling all windows (disallowing user-input to
        prevent re-entrancy of code) and then handling all outstanding
        GUI events.
        
        However, this often triggers an OnPaint() method for wxVTKRWIs,
        resulting in a Render(), resulting in Update() being called whilst
        still in progress.
        """
        self.__RenderWhenDisabled = bool(newValue)

627
628
629
630
631
632

#--------------------------------------------------------------------  
def wxVTKRenderWindowInteractorConeExample():
    """Like it says, just a simple example
    """
    # every wx app needs an app
633
    app = wx.PySimpleApp()
634

635
    # create the top-level frame, sizer and wxVTKRWI
636
    frame = wx.Frame(None, -1, "wxVTKRenderWindowInteractor", size=(400,400))
637
    widget = wxVTKRenderWindowInteractor(frame, -1)
638
639
    sizer = wx.BoxSizer(wx.VERTICAL)
    sizer.Add(widget, 1, wx.EXPAND)
640
641
    frame.SetSizer(sizer)
    frame.Layout()
642

643
644
645
646
647
648
649
650
651
    # It would be more correct (API-wise) to call widget.Initialize() and
    # widget.Start() here, but Initialize() calls RenderWindow.Render().
    # That Render() call will get through before we can setup the 
    # RenderWindow() to render via the wxWidgets-created context; this
    # causes flashing on some platforms and downright breaks things on
    # other platforms.  Instead, we call widget.Enable().  This means
    # that the RWI::Initialized ivar is not set, but in THIS SPECIFIC CASE,
    # that doesn't matter.
    widget.Enable(1)
652

653
    widget.AddObserver("ExitEvent", lambda o,e,f=frame: f.Close())
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669

    ren = vtk.vtkRenderer()
    widget.GetRenderWindow().AddRenderer(ren)

    cone = vtk.vtkConeSource()
    cone.SetResolution(8)
    
    coneMapper = vtk.vtkPolyDataMapper()
    coneMapper.SetInput(cone.GetOutput())
    
    coneActor = vtk.vtkActor()
    coneActor.SetMapper(coneMapper)

    ren.AddActor(coneActor)

    # show the window
670
    frame.Show()
671
672
673
674
675
676

    app.MainLoop()

if __name__ == "__main__":
    wxVTKRenderWindowInteractorConeExample()