""" A simple vtkTkRenderWidget for Tkinter. Created by David Gobbi, April 1999 May ??, 1999 - Modifications peformed by Heather Drury, to rewrite _pan to match method in TkInteractor.tcl May 11, 1999 - Major rewrite by David Gobbi to make the interactor bindings identical to the TkInteractor.tcl bindings. July 14, 1999 - Added modification by Ken Martin for VTK 2.4, to use vtk widgets instead of Togl. Aug 29, 1999 - Renamed file to vtkRenderWidget.py Nov 14, 1999 - Added support for keyword 'rw' Mar 23, 2000 - Extensive but backwards compatible changes, improved documentation """ """ A few important notes: This class is meant to be used as a base-class widget for doing VTK rendering in Python. In VTK (and C++) there is a very important distinction between public ivars (attributes in pythonspeak), protected ivars, and private ivars. When you write a python class that you want to 'look and feel' like a VTK class, you should follow these rules. 1) Attributes should never be public. Attributes should always be either protected (prefixed with a single underscore) or private (prefixed with a double underscore). You can provide access to attributes through public Set/Get methods (same as VTK). 2) Use a single underscore to denote a protected attribute, e.g. self._RenderWindow is protected (can be accessed from this class or a derived class). 3) Use a double underscore to denote a private attribute, e.g. self.__InExpose cannot be accessed outside of this class. All attributes should be 'declared' in the __init__() function i.e. set to some initial value. Don't forget that 'None' means 'NULL' - the python/vtk wrappers guarantee their equivalence. """ import Tkinter import math, os, sys import vtk from vtkLoadPythonTkWidgets import vtkLoadPythonTkWidgets class vtkTkRenderWidget(Tkinter.Widget): """ A vtkTkRenderWidget for Python. Use GetRenderWindow() to get the vtkRenderWindow. Create with the keyword stereo=1 in order to generate a stereo-capable window. Create with the keyword focus_on_enter=1 to enable focus-follows-mouse. The default is for a click-to-focus mode. """ def __init__(self, master, cnf={}, **kw): """ Constructor. Keyword arguments: rw -- Use passed render window instead of creating a new one. stereo -- If True, generate a stereo-capable window. Defaults to False. focus_on_enter -- If True, use a focus-follows-mouse mode. Defaults to False where the widget will use a click-to-focus mode. """ # load the necessary extensions into tk vtkLoadPythonTkWidgets(master.tk) try: # check to see if a render window was specified renderWindow = kw['rw'] except KeyError: renderWindow = vtk.vtkRenderWindow() try: # was a stereo rendering context requested? if kw['stereo']: renderWindow.StereoCapableWindowOn() del kw['stereo'] except KeyError: pass # check if focus should follow mouse if kw.get('focus_on_enter'): self._FocusOnEnter = 1 del kw['focus_on_enter'] else: self._FocusOnEnter = 0 kw['rw'] = renderWindow.GetAddressAsString("vtkRenderWindow") Tkinter.Widget.__init__(self, master, 'vtkTkRenderWidget', cnf, kw) self._CurrentRenderer = None self._CurrentCamera = None self._CurrentZoom = 1.0 self._CurrentLight = None self._ViewportCenterX = 0 self._ViewportCenterY = 0 self._Picker = vtk.vtkCellPicker() self._PickedAssembly = None self._PickedProperty = vtk.vtkProperty() self._PickedProperty.SetColor(1,0,0) self._PrePickedProperty = None self._OldFocus = None # used by the LOD actors self._DesiredUpdateRate = 15 self._StillUpdateRate = 0.0001 # these record the previous mouse position self._LastX = 0 self._LastY = 0 # private attributes self.__InExpose = 0 # create the Tk bindings self.BindTkRenderWidget() def __getattr__(self,attr): # because the tk part of vtkTkRenderWidget must have # the only remaining reference to the RenderWindow when # it is destroyed, we can't actually store the RenderWindow # as an attribute but instead have to get it from the tk-side if attr == '_RenderWindow': return self.GetRenderWindow() raise AttributeError, self.__class__.__name__ + \ " has no attribute named " + attr def BindTkRenderWidget(self): """ Bind some default actions. """ self.bind("", lambda e,s=self: s.StartMotion(e.x,e.y)) self.bind("", lambda e,s=self: s.EndMotion(e.x,e.y)) self.bind("", lambda e,s=self: s.Rotate(e.x,e.y)) self.bind("", lambda e,s=self: s.Pan(e.x,e.y)) self.bind("", lambda e,s=self: s.Zoom(e.x,e.y)) self.bind("", lambda e,s=self: s.Pan(e.x,e.y)) self.bind("", lambda e,s=self: s.Reset(e.x,e.y)) self.bind("", lambda e,s=self: s.deiconify()) self.bind("", lambda e,s=self: s.Wireframe()) self.bind("", lambda e,s=self: s.Surface()) self.bind("", lambda e,s=self: s.PickActor(e.x,e.y)) if self._FocusOnEnter: self.bind("", lambda e,s=self: s.Enter(e.x,e.y)) self.bind("", lambda e,s=self: s.Leave(e.x,e.y)) else: self.bind("", lambda e,s=self: s.Enter(e.x,e.y)) self.bind("", lambda e,s=self: s.Expose()) def GetZoomFactor(self): return self._CurrentZoom def SetDesiredUpdateRate(self, rate): """Mirrors the method with the same name in vtkRenderWindowInteractor.""" self._DesiredUpdateRate = rate def GetDesiredUpdateRate(self): """Mirrors the method with the same name in vtkRenderWindowInteractor.""" return self._DesiredUpdateRate def SetStillUpdateRate(self, rate): """Mirrors the method with the same name in vtkRenderWindowInteractor.""" self._StillUpdateRate = rate def GetStillUpdateRate(self): """Mirrors the method with the same name in vtkRenderWindowInteractor.""" return self._StillUpdateRate def GetRenderWindow(self): addr = self.tk.call(self._w, 'GetRenderWindow')[5:] return vtk.vtkRenderWindow('_%s_vtkRenderWindow_p' % addr) def GetPicker(self): return self._Picker def Expose(self): if (not self.__InExpose): self.__InExpose = 1 self.update() self._RenderWindow.Render() self.__InExpose = 0 def Render(self): if (self._CurrentLight): light = self._CurrentLight light.SetPosition(self._CurrentCamera.GetPosition()) light.SetFocalPoint(self._CurrentCamera.GetFocalPoint()) self._RenderWindow.Render() def UpdateRenderer(self,x,y): """ UpdateRenderer will identify the renderer under the mouse and set up _CurrentRenderer, _CurrentCamera, and _CurrentLight. """ windowX = self.winfo_width() windowY = self.winfo_height() renderers = self._RenderWindow.GetRenderers() numRenderers = renderers.GetNumberOfItems() self._CurrentRenderer = None renderers.InitTraversal() for i in range(0,numRenderers): renderer = renderers.GetNextItem() vx,vy = (0,0) if (windowX > 1): vx = float(x)/(windowX-1) if (windowY > 1): vy = (windowY-float(y)-1)/(windowY-1) (vpxmin,vpymin,vpxmax,vpymax) = renderer.GetViewport() if (vx >= vpxmin and vx <= vpxmax and vy >= vpymin and vy <= vpymax): self._CurrentRenderer = renderer self._ViewportCenterX = float(windowX)*(vpxmax-vpxmin)/2.0\ +vpxmin self._ViewportCenterY = float(windowY)*(vpymax-vpymin)/2.0\ +vpymin self._CurrentCamera = self._CurrentRenderer.GetActiveCamera() lights = self._CurrentRenderer.GetLights() lights.InitTraversal() self._CurrentLight = lights.GetNextItem() break self._LastX = x self._LastY = y def GetCurrentRenderer(self): return self._CurrentRenderer def Enter(self,x,y): self._OldFocus=self.focus_get() self.focus() self.StartMotion(x, y) def Leave(self,x,y): if (self._OldFocus != None): self._OldFocus.focus() def StartMotion(self,x,y): self.GetRenderWindow().SetDesiredUpdateRate(self._DesiredUpdateRate) self.UpdateRenderer(x,y) def EndMotion(self,x,y): self.GetRenderWindow().SetDesiredUpdateRate(self._StillUpdateRate) if self._CurrentRenderer: self.Render() def Rotate(self,x,y): if self._CurrentRenderer: self._CurrentCamera.Azimuth(self._LastX - x) self._CurrentCamera.Elevation(y - self._LastY) self._CurrentCamera.OrthogonalizeViewUp() self._LastX = x self._LastY = y self._CurrentRenderer.ResetCameraClippingRange() self.Render() def Pan(self,x,y): if self._CurrentRenderer: renderer = self._CurrentRenderer camera = self._CurrentCamera (pPoint0,pPoint1,pPoint2) = camera.GetPosition() (fPoint0,fPoint1,fPoint2) = camera.GetFocalPoint() if (camera.GetParallelProjection()): renderer.SetWorldPoint(fPoint0,fPoint1,fPoint2,1.0) renderer.WorldToDisplay() fx,fy,fz = renderer.GetDisplayPoint() renderer.SetDisplayPoint(fx-x+self._LastX, fy+y-self._LastY, fz) renderer.DisplayToWorld() fx,fy,fz,fw = renderer.GetWorldPoint() camera.SetFocalPoint(fx,fy,fz) renderer.SetWorldPoint(pPoint0,pPoint1,pPoint2,1.0) renderer.WorldToDisplay() fx,fy,fz = renderer.GetDisplayPoint() renderer.SetDisplayPoint(fx-x+self._LastX, fy+y-self._LastY, fz) renderer.DisplayToWorld() fx,fy,fz,fw = renderer.GetWorldPoint() camera.SetPosition(fx,fy,fz) else: (fPoint0,fPoint1,fPoint2) = camera.GetFocalPoint() # Specify a point location in world coordinates renderer.SetWorldPoint(fPoint0,fPoint1,fPoint2,1.0) renderer.WorldToDisplay() # Convert world point coordinates to display coordinates dPoint = renderer.GetDisplayPoint() focalDepth = dPoint[2] aPoint0 = self._ViewportCenterX + (x - self._LastX) aPoint1 = self._ViewportCenterY - (y - self._LastY) renderer.SetDisplayPoint(aPoint0,aPoint1,focalDepth) renderer.DisplayToWorld() (rPoint0,rPoint1,rPoint2,rPoint3) = renderer.GetWorldPoint() if (rPoint3 != 0.0): rPoint0 = rPoint0/rPoint3 rPoint1 = rPoint1/rPoint3 rPoint2 = rPoint2/rPoint3 camera.SetFocalPoint((fPoint0 - rPoint0) + fPoint0, (fPoint1 - rPoint1) + fPoint1, (fPoint2 - rPoint2) + fPoint2) camera.SetPosition((fPoint0 - rPoint0) + pPoint0, (fPoint1 - rPoint1) + pPoint1, (fPoint2 - rPoint2) + pPoint2) self._LastX = x self._LastY = y self.Render() def Zoom(self,x,y): if self._CurrentRenderer: renderer = self._CurrentRenderer camera = self._CurrentCamera zoomFactor = math.pow(1.02,(0.5*(self._LastY - y))) self._CurrentZoom = self._CurrentZoom * zoomFactor if camera.GetParallelProjection(): parallelScale = camera.GetParallelScale()/zoomFactor camera.SetParallelScale(parallelScale) else: camera.Dolly(zoomFactor) renderer.ResetCameraClippingRange() self._LastX = x self._LastY = y self.Render() def Reset(self,x,y): if self._CurrentRenderer: self._CurrentRenderer.ResetCamera() self.Render() def Wireframe(self): actors = self._CurrentRenderer.GetActors() numActors = actors.GetNumberOfItems() actors.InitTraversal() for i in range(0,numActors): actor = actors.GetNextItem() actor.GetProperty().SetRepresentationToWireframe() self.Render() def Surface(self): actors = self._CurrentRenderer.GetActors() numActors = actors.GetNumberOfItems() actors.InitTraversal() for i in range(0,numActors): actor = actors.GetNextItem() actor.GetProperty().SetRepresentationToSurface() self.Render() def PickActor(self,x,y): if self._CurrentRenderer: renderer = self._CurrentRenderer picker = self._Picker windowY = self.winfo_height() picker.Pick(x,(windowY - y - 1),0.0,renderer) assembly = picker.GetAssembly() if (self._PickedAssembly != None and self._PrePickedProperty != None): self._PickedAssembly.SetProperty(self._PrePickedProperty) # release hold of the property self._PrePickedProperty.UnRegister(self._PrePickedProperty) self._PrePickedProperty = None if (assembly != None): self._PickedAssembly = assembly self._PrePickedProperty = self._PickedAssembly.GetProperty() # hold onto the property self._PrePickedProperty.Register(self._PrePickedProperty) self._PickedAssembly.SetProperty(self._PickedProperty) self.Render() #---------------------------------------------------------------------------- def vtkRenderWidgetConeExample(): """Like it says, just a simple example """ # create root window root = Tkinter.Tk() # create vtkTkRenderWidget pane = vtkTkRenderWidget(root,width=300,height=300) ren = vtk.vtkRenderer() pane.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) # pack the pane into the tk root pane.pack() # start the tk mainloop root.mainloop() if __name__ == "__main__": vtkRenderWidgetConeExample()