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