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