QVTKRenderWidget.py 14.9 KB
Newer Older
1
2
3
"""
A simple VTK input file for PyQt, the qt bindings for python.
See http://www.trolltech.com for qt documentation, and
Ken Martin's avatar
Ken Martin committed
4
5
http://www.river-bank.demon.co.uk or http://www.thekompany.com
for the qt python bindings.
6

7
8
9
10
11
12
13
14
15
16
*******************************************************

   NOTE: The widget provided by this module is not free of bugs and it
   is recommended that you consider using the
   QVTKRenderWindowInteractor widget that is also in this directory
   instead of this one.

*******************************************************


17
18
Created by David Gobbi, December 2001
Based on vtkTkRenderWindget.py
Ken Martin's avatar
Ken Martin committed
19
20
21

Changes by Gerard Vermeulen Feb. 2003
 Win32 support
22
23
24
"""

"""
Ken Martin's avatar
Ken Martin committed
25
26
This class should work with both the UNIX version of Qt and also on
Win32.
27
28
29
30
31
32
33
34

Depending on the OpenGL graphics drivers, it may not
be possible to have more than one QVTKRenderWidget
per application.

In short, this class is experimental.  A proper implementation
will probably require a QVTKRenderWidget that is written in
C++ and then wrapped to be made available through python,
35
similar to the vtkTkRenderWidget.
36
37
"""

Ken Martin's avatar
Ken Martin committed
38
39
40
41
# Problems on Win32:
# 1. The widget is not cleaned up properly and crashes the
#    application.

42
import vtk
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import math, os, sys
from qt import *

class QVTKRenderWidget(QWidget):
    """
    A QVTKRenderWidget for Python and Qt.
    Use GetRenderWindow() to get the vtkRenderWindow.
    Create with the keyword stereo=1 in order to
    generate a stereo-capable window.
    """
    def __init__(self, parent=None, name=None, *args, **kw):

        # miscellaneous protected variables
        self._CurrentRenderer = None
        self._CurrentCamera = None
        self._CurrentZoom = 1.0
        self._CurrentLight = None

        self._ViewportCenterX = 0
        self._ViewportCenterY = 0
63

64
        self._Picker = vtk.vtkCellPicker()
65
        self._PickedActor = None
66
        self._PickedProperty = vtk.vtkProperty()
67
68
        self._PickedProperty.SetColor(1,0,0)
        self._PrePickedProperty = None
69

70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
        # these record the previous mouse position
        self._LastX = 0
        self._LastY = 0

        # the current interaction mode (Rotate, Pan, Zoom, etc)
        self._Mode = None
        self._ActiveButton = 0

        # used by the LOD actors
        self._DesiredUpdateRate = 15
        self._StillUpdateRate = 0.0001

        # private attributes
        self.__oldFocus = None
        self.__saveX = 0
        self.__saveY = 0
        self.__saveState = 0
        self.__connected = 0  # is QT->VTK connection done?

        # do special handling of some keywords:
        # stereo, rw
91

92
        stereo = 0
93

94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
        if kw.has_key('stereo'):
            if kw['stereo']:
                stereo = 1
            del kw['stereo']

        rw = None

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

        # create qt-level widget
        apply(QWidget.__init__, (self,parent,name) + args, kw)

        if rw: # user-supplied render window
            self._RenderWindow = rw
        else:
111
            self._RenderWindow = vtk.vtkRenderWindow()
112
113
114
115

        if stereo: # stereo mode
            self._RenderWindow.StereoCapableWindowOn()
            self._RenderWindow.SetStereoTypeToCrystalEyes()
116

117
118
119
120
121
122
        # do all the necessary qt setup
        self.setBackgroundMode(2) # NoBackground
        self.setMouseTracking(1) # get all mouse events
        self.setFocusPolicy(2) # ClickFocus
        if parent == None:
            self.show()
123

124
125
126
127
        if self.isVisible():
            if self.__connected == 0:
                size = self.size()
                self._RenderWindow.SetSize(size.width(),size.height())
Ken Martin's avatar
Ken Martin committed
128
                self._RenderWindow.SetWindowInfo(str(int(self.winId())))
129
130
131
                self.__connected = 1


Ken Martin's avatar
Ken Martin committed
132
133
134
135
    def show(self):
        QWidget.show(self)
        self.repaint() # needed for initial contents display on Win32

136
137
138
139
140
    def paintEvent(self,ev):
        if self.isVisible():
            if self.__connected == 0:
                size = self.size()
                self._RenderWindow.SetSize(size.width(),size.height())
Ken Martin's avatar
Ken Martin committed
141
                self._RenderWindow.SetWindowInfo(str(int(self.winId())))
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
                self.__connected = 1
        if self.__connected:
            self.Render()

    def resizeEvent(self,ev):
        self.repaint()

    def enterEvent(self,ev):
        if not self.hasFocus():
            self.__oldFocus = self.focusWidget()
            self.setFocus()

    def leaveEvent(self,ev):
        if (self.__saveState & 0x7) == 0 and self.__oldFocus:
            self.__oldFocus.setFocus()
            self.__oldFocus = None

    def mousePressEvent(self,ev):
        if self._Mode != None:
            return

163
        if (ev.button() == 2 or
164
165
166
167
168
169
170
171
172
173
174
175
176
            ev.button() == 1 and ev.state() & 16):
            self._Mode = "Zoom"
            self._ActiveButton = ev.button()
        elif (ev.button() == 4 or
              ev.button() == 1 and ev.state() & 8):
            self._Mode = "Pan"
            self._ActiveButton = ev.button()
        elif (ev.button() == 1):
            self._Mode = "Rotate"
            self._ActiveButton = ev.button()

        if self._Mode != None:
            self._RenderWindow.SetDesiredUpdateRate(self._DesiredUpdateRate)
177

178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
        self.UpdateRenderer(ev.x(),ev.y())

    def mouseReleaseEvent(self,ev):
        if self._Mode == None:
            return

        self._RenderWindow.SetDesiredUpdateRate(self._StillUpdateRate)

        if self._CurrentRenderer:
            self.Render()
        if ev.button() == self._ActiveButton:
            self._Mode = None
            self._ActiveButton = 0

    def mouseMoveEvent(self,ev):
        self.__saveState = ev.state()
        self.__saveX = ev.x()
        self.__saveY = ev.y()
        if self._Mode == "Pan":
            self.Pan(ev.x(),ev.y())
        elif self._Mode == "Rotate":
            self.Rotate(ev.x(),ev.y())
        elif self._Mode == "Zoom":
            self.Zoom(ev.x(),ev.y())

    def keyPressEvent(self,ev):
        if ev.key() == ord('R'):
            self.Reset(self.__saveX,self.__saveY)
        if ev.key() == ord('W'):
            self.Wireframe()
        if ev.key() == ord('S'):
            self.Surface()
        if ev.key() == ord('P'):
            self.PickActor(self.__saveX,self.__saveY)
212

Clinton Stimpson's avatar
 
Clinton Stimpson committed
213
214
    def contextMenuEvent(self,ev):
        ev.accept();
215
216
217
218
219
220
221
222
223

    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."""
224
225
        return self._DesiredUpdateRate

226
227
228
229
230
231
232
233
    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."""
234
        return self._StillUpdateRate
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273

    def GetZoomFactor(self):
        return self._CurrentZoom

    def GetRenderWindow(self):
        return self._RenderWindow

    def GetPicker(self):
        return self._Picker

    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.width()
        windowY = self.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()
274

275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
            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
293

294
295
    def Rotate(self,x,y):
        if self._CurrentRenderer:
296

297
298
299
            self._CurrentCamera.Azimuth(self._LastX - x)
            self._CurrentCamera.Elevation(y - self._LastY)
            self._CurrentCamera.OrthogonalizeViewUp()
300

301
302
            self._LastX = x
            self._LastY = y
303

304
305
306
307
308
            self._CurrentRenderer.ResetCameraClippingRange()
            self.Render()

    def Pan(self,x,y):
        if self._CurrentRenderer:
309

310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
            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)
335

336
337
338
339
340
341
342
343
            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]
344

345
346
                aPoint0 = self._ViewportCenterX + (x - self._LastX)
                aPoint1 = self._ViewportCenterY - (y - self._LastY)
347

348
349
                renderer.SetDisplayPoint(aPoint0,aPoint1,focalDepth)
                renderer.DisplayToWorld()
350

351
352
353
354
355
356
                (rPoint0,rPoint1,rPoint2,rPoint3) = renderer.GetWorldPoint()
                if (rPoint3 != 0.0):
                    rPoint0 = rPoint0/rPoint3
                    rPoint1 = rPoint1/rPoint3
                    rPoint2 = rPoint2/rPoint3

357
                camera.SetFocalPoint((fPoint0 - rPoint0) + fPoint0,
358
                                     (fPoint1 - rPoint1) + fPoint1,
359
360
361
                                     (fPoint2 - rPoint2) + fPoint2)

                camera.SetPosition((fPoint0 - rPoint0) + pPoint0,
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
                                   (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()
394

395
396
397
398
399
400
401
402
403
404
405
        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()
406

407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
    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
422

423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
            windowY = self.height()
            picker.Pick(x,(windowY - y - 1),0.0,renderer)
            actor = picker.GetActor()

            if (self._PickedActor != None and
                self._PrePickedProperty != None):
                self._PickedActor.SetProperty(self._PrePickedProperty)
                # release hold of the property
                self._PrePickedProperty.UnRegister(self._PrePickedProperty)
                self._PrePickedProperty = None

            if (actor != None):
                self._PickedActor = actor
                self._PrePickedProperty = self._PickedActor.GetProperty()
                # hold onto the property
                self._PrePickedProperty.Register(self._PrePickedProperty)
                self._PickedActor.SetProperty(self._PickedProperty)

            self.Render()

443
#----------------------------------------------------------------------------
444
445
446
447
448
449
450
451
452
def QVTKRenderWidgetConeExample():
    """Like it says, just a simple example
    """
    # every QT app needs an app
    app = QApplication(['QVTKRenderWidget'])

    # create the widget
    widget = QVTKRenderWidget()

453
    ren = vtk.vtkRenderer()
454
455
    widget.GetRenderWindow().AddRenderer(ren)

456
    cone = vtk.vtkConeSource()
457
    cone.SetResolution(8)
458

459
    coneMapper = vtk.vtkPolyDataMapper()
460
    coneMapper.SetInputConnection(cone.GetOutputPort())
461

462
    coneActor = vtk.vtkActor()
463
464
465
466
467
468
469
470
471
472
    coneActor.SetMapper(coneMapper)

    ren.AddActor(coneActor)

    # show the widget
    widget.show()
    # close the application when window is closed
    qApp.setMainWidget(widget)
    # start event processing
    app.exec_loop()
473

474
475
476
if __name__ == "__main__":
    QVTKRenderWidgetConeExample()