[vtkusers] Segfault in vtkOpenGLRenderWindow.OpenGLInitState and advice about render window in/out events

Elvis Stansvik elvis.stansvik at orexplore.com
Sun Jun 26 07:06:44 EDT 2016


Hi,

In my quest to create a PyQt5 widget based on QOpenGLWidget for
showing/interacting with VTK, I now have something that seems to work
(including the whole thing as a runnable example below).

I do however have two problems (indicated by "FIXME:"s in the code below):

1. When I try to call vtkOpenGLRenderWindow.OpenGLInitState when handling
the render window StartEvent (commented out below), similar to how
QVTKWidget2 does, I get a segmentation fault:

(gdb) bt
#0  0x0000000000000000 in ?? ()
#1  0x00007fffd8ad13b9 in vtkOpenGLRenderWindow::OpenGLInitState() () from
/usr/lib/libvtkRenderingOpenGL2.so.1
#2  0x00007fffd8de8479 in ?? () from
/usr/lib/libvtkRenderingOpenGL2Python35D.so.1
#3  0x00007ffff79ba5e9 in PyCFunction_Call () from
/usr/lib/libpython3.5m.so.1.0
#4  0x00007ffff7a325f1 in PyEval_EvalFrameEx () from
/usr/lib/libpython3.5m.so.1.0
#5  0x00007ffff7a339e2 in ?? () from /usr/lib/libpython3.5m.so.1.0
#6  0x00007ffff7a33ac3 in PyEval_EvalCodeEx () from
/usr/lib/libpython3.5m.so.1.0
#7  0x00007ffff799db98 in ?? () from /usr/lib/libpython3.5m.so.1.0
#8  0x00007ffff79730da in PyObject_Call () from
/usr/lib/libpython3.5m.so.1.0
#9  0x00007ffff7989d64 in ?? () from /usr/lib/libpython3.5m.so.1.0
#10 0x00007ffff79730da in PyObject_Call () from
/usr/lib/libpython3.5m.so.1.0
#11 0x00007ffff7a2a457 in PyEval_CallObjectWithKeywords () from
/usr/lib/libpython3.5m.so.1.0
#12 0x00007fffe8d905a0 in vtkPythonCommand::Execute(vtkObject*, unsigned
long, void*) () from /usr/lib/libvtkWrappingPython35Core.so.1
#13 0x00007fffe89ac196 in ?? () from /usr/lib/libvtkCommonCore.so.1
#14 0x00007fffdf022a0f in vtkRenderWindow::Render() () from
/usr/lib/libvtkRenderingCore.so.1
#15 0x00007fffd8ad4461 in vtkOpenGLRenderWindow::Render() () from
/usr/lib/libvtkRenderingOpenGL2.so.1
#16 0x00007fffdf027498 in vtkRenderWindowInteractor::Start() () from
/usr/lib/libvtkRenderingCore.so.1
#17 0x00007fffdf3ee789 in ?? () from
/usr/lib/libvtkRenderingCorePython35D.so.1
#18 0x00007ffff79ba5e9 in PyCFunction_Call () from
/usr/lib/libpython3.5m.so.1.0
#19 0x00007ffff7a325f1 in PyEval_EvalFrameEx () from
/usr/lib/libpython3.5m.so.1.0
#20 0x00007ffff7a339e2 in ?? () from /usr/lib/libpython3.5m.so.1.0
#21 0x00007ffff7a33ac3 in PyEval_EvalCodeEx () from
/usr/lib/libpython3.5m.so.1.0
#22 0x00007ffff799db98 in ?? () from /usr/lib/libpython3.5m.so.1.0
#23 0x00007ffff79730da in PyObject_Call () from
/usr/lib/libpython3.5m.so.1.0
#24 0x00007ffff7989d64 in ?? () from /usr/lib/libpython3.5m.so.1.0
#25 0x00007ffff79730da in PyObject_Call () from
/usr/lib/libpython3.5m.so.1.0
#26 0x00007ffff79cfa10 in ?? () from /usr/lib/libpython3.5m.so.1.0
#27 0x00007ffff79cdde6 in ?? () from /usr/lib/libpython3.5m.so.1.0
#28 0x00007ffff79730da in PyObject_Call () from
/usr/lib/libpython3.5m.so.1.0
#29 0x00007ffff7a2c4bc in PyEval_EvalFrameEx () from
/usr/lib/libpython3.5m.so.1.0
#30 0x00007ffff7a32942 in PyEval_EvalFrameEx () from
/usr/lib/libpython3.5m.so.1.0
#31 0x00007ffff7a339e2 in ?? () from /usr/lib/libpython3.5m.so.1.0
#32 0x00007ffff7a33ac3 in PyEval_EvalCodeEx () from
/usr/lib/libpython3.5m.so.1.0
#33 0x00007ffff7a33aeb in PyEval_EvalCode () from
/usr/lib/libpython3.5m.so.1.0
#34 0x00007ffff7a2802d in ?? () from /usr/lib/libpython3.5m.so.1.0
#35 0x00007ffff79ba5e9 in PyCFunction_Call () from
/usr/lib/libpython3.5m.so.1.0
#36 0x00007ffff7a325f1 in PyEval_EvalFrameEx () from
/usr/lib/libpython3.5m.so.1.0
#37 0x00007ffff7a339e2 in ?? () from /usr/lib/libpython3.5m.so.1.0
#38 0x00007ffff7a306e0 in PyEval_EvalFrameEx () from
/usr/lib/libpython3.5m.so.1.0
#39 0x00007ffff7a339e2 in ?? () from /usr/lib/libpython3.5m.so.1.0
#40 0x00007ffff7a33ac3 in PyEval_EvalCodeEx () from
/usr/lib/libpython3.5m.so.1.0
#41 0x00007ffff799db98 in ?? () from /usr/lib/libpython3.5m.so.1.0
#42 0x00007ffff79730da in PyObject_Call () from
/usr/lib/libpython3.5m.so.1.0
#43 0x00007ffff7a6af01 in ?? () from /usr/lib/libpython3.5m.so.1.0
#44 0x00007ffff7a6b7dc in Py_Main () from /usr/lib/libpython3.5m.so.1.0
#45 0x0000000000400ae7 in main ()
(gdb)

Sorry for the lack of debugging symbols, but before I recompile VTK with
debugging symbols, I thought I'd ask if anyone has an idea what may be the
reason for the crash? I haven't seen any adverse effects from not calling
this function, but I suspect something is broken? (how can I test?)

2. I can't seem to find a way to properly implement the
WindowIsCurrentEvent, WindowIsDirectEvent and WindowSupportsOpenGLEvent
render window events. E.g. (from below):

    def _onWindowIsCurrentEvent(self, sender, event, callData):
        callData = True  # FIXME: This won't work...

    def _onWindowIsDirectEvent(self, sender, event, callData):
        callData = True  # FIXME: This won't work...

    def _onWindowSupportsOpenGLEvent(self, sender, event, callData):
        callData = True  # FIXME: This won't work...

The callData is a bool* and the handler is expected to write a bool to the
pointed memory. I can't see that there's any way to do this from Python (is
it possible with ctypes somehow?).

So far I haven't seen any adverse effects from not implementing these three
events correctly, but expect something to be broken..?

Thankful for any advice on these two issues, and it would be great if
someone could try out the example, with the

    # self.renderWindow.OpenGLInitState()  # FIXME: Segfaults :(

line uncommented, to confirm the segfault I'm seeing.

Cheers,
Elvis


from PyQt5.QtCore import Qt, QTimer, QEvent, QSize
from PyQt5.QtGui import QSurfaceFormat
from PyQt5.QtWidgets import QWidget, QOpenGLWidget

from vtk import vtkGenericOpenGLRenderWindow
from vtk import vtkGenericRenderWindowInteractor
from vtk import vtkCommand
from vtk import vtkRenderer

from vtk import VTK_CURSOR_DEFAULT, VTK_CURSOR_ARROW, VTK_CURSOR_SIZENE
from vtk import VTK_CURSOR_SIZENW, VTK_CURSOR_SIZESW, VTK_CURSOR_SIZESE
from vtk import VTK_CURSOR_SIZENS, VTK_CURSOR_SIZEWE, VTK_CURSOR_SIZEALL
from vtk import VTK_CURSOR_HAND, VTK_CURSOR_CROSSHAIR


class VTKWidget(QOpenGLWidget):

    def __init__(self, surfaceFormat=QSurfaceFormat.defaultFormat(), *args,
**kwargs):
        super().__init__(*args, **kwargs)

        # Request OpenGL 3.2 compatibility profile and enable multi-sampling
        surfaceFormat.setMajorVersion(3)
        surfaceFormat.setMinorVersion(2)
        surfaceFormat.setProfile(QSurfaceFormat.CompatibilityProfile)
        surfaceFormat.setSamples(8)
        self.setFormat(surfaceFormat)

        # Create render window
        self.renderWindow = vtkGenericOpenGLRenderWindow()
        self.renderWindow.SetSize(self.width(), self.height())
        self.renderWindow.SetPosition(self.x(), self.y())

        self.renderWindow.AddObserver(vtkCommand.StartEvent,
                                      self._onStartEvent)
        self.renderWindow.AddObserver(vtkCommand.EndEvent,
                                      self._onEndEvent)
        self.renderWindow.AddObserver(vtkCommand.CursorChangedEvent,
                                      self._onCursorChangedEvent)
        self.renderWindow.AddObserver(vtkCommand.WindowMakeCurrentEvent,
                                      self._onWindowMakeCurrentEvent)
        self.renderWindow.AddObserver(vtkCommand.WindowIsCurrentEvent,
                                      self._onWindowIsCurrentEvent)
        self.renderWindow.AddObserver(vtkCommand.WindowFrameEvent,
                                      self._onWindowFrameEvent)
        self.renderWindow.AddObserver(vtkCommand.WindowIsDirectEvent,
                                      self._onWindowIsDirectEvent)
        self.renderWindow.AddObserver(vtkCommand.WindowSupportsOpenGLEvent,
                                      self._onWindowSupportsOpenGLEvent)

        # Create interactor
        self.interactor = vtkGenericRenderWindowInteractor()
        self.interactor.SetRenderWindow(self.renderWindow)
        self.interactor.SetSize(self.width(), self.height())
        self.interactor.AddObserver(vtkCommand.CreateTimerEvent,
                                    lambda *_: self._timer.start(10))
        self.interactor.AddObserver(vtkCommand.DestroyTimerEvent,
                                    lambda *_: self._timer.stop())
        self.interactor.Start()

        # Create renderer
        self.renderer = vtkRenderer()
        self.renderWindow.AddRenderer(self.renderer)

        # Timer for the interactor
        self._timer = QTimer(self)
        self._timer.timeout.connect(self.interactor.TimerEvent)

        # Some tracked input state
        self._pressedButton = Qt.NoButton
        self._lastMouseX = 0
        self._lastMouseY = 0
        self._lastModifiers = Qt.NoModifier
        self._lastButtons = Qt.NoButton
        self._wheelDelta = 0

        # Finalize render window before we're destroyed
        self._hidden = QWidget(self)
        self._hidden.hide()
        self._hidden.destroyed.connect(self.renderWindow.Finalize)

    #
    # Handle VTK render window events
    #

    def _onStartEvent(self, *_):
        self.makeCurrent()
        self.renderWindow.PushState()
        # self.renderWindow.OpenGLInitState()  # FIXME: Segfaults :(

    def _onEndEvent(self, *_):
        self.renderWindow.PopState()

    def _onWindowMakeCurrentEvent(self, *_):
        pass  # Handled automatically by QOpenGLWidget

    def _onWindowIsCurrentEvent(self, sender, event, callData):
        callData = True  # FIXME: This won't work...

    def _onWindowIsDirectEvent(self, sender, event, callData):
        callData = True  # FIXME: This won't work...

    def _onWindowSupportsOpenGLEvent(self, sender, event, callData):
        callData = True  # FIXME: This won't work...

    def _onWindowFrameEvent(self, *_):
        if self.renderWindow.GetSwapBuffers():
            self.update()

    def _onCursorChangedEvent(self):

        def showCursor():
            vtkCursor = self.renderWindow.GetCurrentCursor()
            qtCursor = VTKWidget._cursors.get(vtkCursor, Qt.ArrowCursor)
            self.setCursor(qtCursor)

        # Defer until cursor has actually changed
        QTimer.singleShot(0, showCursor)

    #
    # Implement QOpenGLWidget functions
    #

    def initializeGL(self):
        self.renderWindow.OpenGLInitContext()

    def paintGL(self):
        self.interactor.Render()

    def resizeGL(self, width, height):
        self.renderWindow.SetSize(width, height)
        self.interactor.SetSize(width, height)
        self.interactor.ConfigureEvent()

    #
    # Handle QWidget events
    #

    def closeEvent(self, _):
        self.renderWindow.Finalize()

    def sizeHint(self):
        return QSize(400, 400)

    def moveEvent(self, event):
        super().moveEvent(event)
        self.renderWindow.SetPosition(self.x(), self.y())

    def enterEvent(self, event):
        ctrl, shift = self._modifiers(event)
        self.interactor.SetEventInformationFlipY(self._lastMouseX,
self._lastMouseY,
                                                 ctrl, shift, chr(0), 0,
None)
        self.interactor.EnterEvent()

    def leaveEvent(self, event):
        ctrl, shift = self._modifiers(event)
        self.interactor.SetEventInformationFlipY(self._lastMouseX,
self._lastMouseY,
                                                 ctrl, shift, chr(0), 0,
None)
        self.interactor.LeaveEvent()

    def mousePressEvent(self, event):
        ctrl, shift = self._modifiers(event)
        repeat = 1 if event.type() == QEvent.MouseButtonDblClick else 0
        self.interactor.SetEventInformationFlipY(event.x(), event.y(),
ctrl, shift,
                                                 chr(0), repeat, None)

        self._pressedButton = event.button()

        if self._pressedButton == Qt.LeftButton:
            self.interactor.LeftButtonPressEvent()
        elif self._pressedButton == Qt.RightButton:
            self.interactor.RightButtonPressEvent()
        elif self._pressedButton == Qt.MidButton:
            self.interactor.MiddleButtonPressEvent()

    def mouseReleaseEvent(self, event):
        ctrl, shift = self._modifiers(event)
        self.interactor.SetEventInformationFlipY(event.x(), event.y(),
ctrl, shift,
                                                 chr(0), 0, None)

        if self._pressedButton == Qt.LeftButton:
            self.interactor.LeftButtonReleaseEvent()
        elif self._pressedButton == Qt.RightButton:
            self.interactor.RightButtonReleaseEvent()
        elif self._pressedButton == Qt.MidButton:
            self.interactor.MiddleButtonReleaseEvent()

        self._pressedButton = Qt.NoButton

    def mouseMoveEvent(self, event):
        self._lastModifiers = event.modifiers()
        self._lastButtons = event.buttons()
        self._lastMouseX = event.x()
        self._lastMouseY = event.y()

        ctrl, shift = self._modifiers(event)
        self.interactor.SetEventInformationFlipY(event.x(), event.y(),
ctrl, shift,
                                                 chr(0), 0, None)
        self.interactor.MouseMoveEvent()

    def keyPressEvent(self, event):
        ctrl, shift = self._modifiers(event)
        key = str(event.text()) if event.key() < 256 else chr(0)
        keySym = VTKWidget._qtKeyToKeySymbol(event.key())

        if shift and len(keySym) == 1 and keySym.isalpha():
            keySym = keySym.upper()

        self.interactor.SetEventInformationFlipY(self._lastMouseX,
self._lastMouseY,
                                                 ctrl, shift, key, 0,
keySym)
        self.interactor.KeyPressEvent()
        self.interactor.CharEvent()

    def keyReleaseEvent(self, event):
        ctrl, shift = self._modifiers(event)
        key = chr(event.key()) if event.key() < 256 else chr(0)

        self.interactor.SetEventInformationFlipY(self._lastMouseX,
self._lastMouseY,
                                                 ctrl, shift, key, 0, None)
        self.interactor.KeyReleaseEvent()

    def wheelEvent(self, event):
        self._wheelDelta += event.angleDelta().y()

        if self._wheelDelta >= 120:
            self.interactor.MouseWheelForwardEvent()
            self._wheelDelta = 0
        elif self._wheelDelta <= -120:
            self.interactor.MouseWheelBackwardEvent()
            self._wheelDelta = 0

    def _modifiers(self, event):
        ctrl = shift = False

        if hasattr(event, 'modifiers'):
            if event.modifiers() & Qt.ShiftModifier:
                shift = True
            if event.modifiers() & Qt.ControlModifier:
                ctrl = True
        else:
            if self._lastModifiers & Qt.ShiftModifier:
                shift = True
            if self._lastModifiers & Qt.ControlModifier:
                ctrl = True

        return ctrl, shift

    @classmethod
    def _qtKeyToKeySymbol(cls, key):
        if key not in VTKWidget._keySymbols:
            return None
        return VTKWidget._keySymbols[key]

    # Maps VTK cursors to Qt cursors
    _cursors = {
        VTK_CURSOR_DEFAULT: Qt.ArrowCursor,
        VTK_CURSOR_ARROW: Qt.ArrowCursor,
        VTK_CURSOR_SIZENE: Qt.SizeBDiagCursor,
        VTK_CURSOR_SIZENW: Qt.SizeFDiagCursor,
        VTK_CURSOR_SIZESW: Qt.SizeBDiagCursor,
        VTK_CURSOR_SIZESE: Qt.SizeFDiagCursor,
        VTK_CURSOR_SIZENS: Qt.SizeVerCursor,
        VTK_CURSOR_SIZEWE: Qt.SizeHorCursor,
        VTK_CURSOR_SIZEALL: Qt.SizeAllCursor,
        VTK_CURSOR_HAND: Qt.PointingHandCursor,
        VTK_CURSOR_CROSSHAIR: Qt.CrossCursor
    }

    # Maps Qt keys to VTK key symbols
    _keySymbols = {
        Qt.Key_Backspace: 'BackSpace',
        Qt.Key_Tab: 'Tab',
        Qt.Key_Backtab: 'Tab',
        # Qt.Key_Clear : 'Clear',
        Qt.Key_Return: 'Return',
        Qt.Key_Enter: 'Return',
        Qt.Key_Shift: 'Shift_L',
        Qt.Key_Control: 'Control_L',
        Qt.Key_Alt: 'Alt_L',
        Qt.Key_Pause: 'Pause',
        Qt.Key_CapsLock: 'Caps_Lock',
        Qt.Key_Escape: 'Escape',
        Qt.Key_Space: 'space',
        # Qt.Key_Prior : 'Prior',
        # Qt.Key_Next : 'Next',
        Qt.Key_End: 'End',
        Qt.Key_Home: 'Home',
        Qt.Key_Left: 'Left',
        Qt.Key_Up: 'Up',
        Qt.Key_Right: 'Right',
        Qt.Key_Down: 'Down',
        Qt.Key_SysReq: 'Snapshot',
        Qt.Key_Insert: 'Insert',
        Qt.Key_Delete: 'Delete',
        Qt.Key_Help: 'Help',
        Qt.Key_0: '0',
        Qt.Key_1: '1',
        Qt.Key_2: '2',
        Qt.Key_3: '3',
        Qt.Key_4: '4',
        Qt.Key_5: '5',
        Qt.Key_6: '6',
        Qt.Key_7: '7',
        Qt.Key_8: '8',
        Qt.Key_9: '9',
        Qt.Key_A: 'a',
        Qt.Key_B: 'b',
        Qt.Key_C: 'c',
        Qt.Key_D: 'd',
        Qt.Key_E: 'e',
        Qt.Key_F: 'f',
        Qt.Key_G: 'g',
        Qt.Key_H: 'h',
        Qt.Key_I: 'i',
        Qt.Key_J: 'j',
        Qt.Key_K: 'k',
        Qt.Key_L: 'l',
        Qt.Key_M: 'm',
        Qt.Key_N: 'n',
        Qt.Key_O: 'o',
        Qt.Key_P: 'p',
        Qt.Key_Q: 'q',
        Qt.Key_R: 'r',
        Qt.Key_S: 's',
        Qt.Key_T: 't',
        Qt.Key_U: 'u',
        Qt.Key_V: 'v',
        Qt.Key_W: 'w',
        Qt.Key_X: 'x',
        Qt.Key_Y: 'y',
        Qt.Key_Z: 'z',
        Qt.Key_Asterisk: 'asterisk',
        Qt.Key_Plus: 'plus',
        Qt.Key_Minus: 'minus',
        Qt.Key_Period: 'period',
        Qt.Key_Slash: 'slash',
        Qt.Key_F1: 'F1',
        Qt.Key_F2: 'F2',
        Qt.Key_F3: 'F3',
        Qt.Key_F4: 'F4',
        Qt.Key_F5: 'F5',
        Qt.Key_F6: 'F6',
        Qt.Key_F7: 'F7',
        Qt.Key_F8: 'F8',
        Qt.Key_F9: 'F9',
        Qt.Key_F10: 'F10',
        Qt.Key_F11: 'F11',
        Qt.Key_F12: 'F12',
        Qt.Key_F13: 'F13',
        Qt.Key_F14: 'F14',
        Qt.Key_F15: 'F15',
        Qt.Key_F16: 'F16',
        Qt.Key_F17: 'F17',
        Qt.Key_F18: 'F18',
        Qt.Key_F19: 'F19',
        Qt.Key_F20: 'F20',
        Qt.Key_F21: 'F21',
        Qt.Key_F22: 'F22',
        Qt.Key_F23: 'F23',
        Qt.Key_F24: 'F24',
        Qt.Key_NumLock: 'Num_Lock',
        Qt.Key_ScrollLock: 'Scroll_Lock'
    }

def example():
    #
    # Try out VTKWidget by showing a couple of cones
    #
    from sys import argv, exit
    from PyQt5.QtWidgets import QApplication, QVBoxLayout
    from vtk import vtkConeSource, vtkPolyDataMapper, vtkActor

    app = QApplication(argv)

    layout = QVBoxLayout()

    for i in range(2):
        widget = VTKWidget()

        coneSource = vtkConeSource()
        coneSource.SetResolution(8)

        coneMapper = vtkPolyDataMapper()
        coneMapper.SetInputConnection(coneSource.GetOutputPort())

        coneActor = vtkActor()
        coneActor.SetMapper(coneMapper)

        widget.renderer.AddActor(coneActor)

        layout.addWidget(widget)

    container = QWidget()
    container.setLayout(layout)
    container.show()

    exit(app.exec_())


if __name__ == '__main__':
    example()
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://public.kitware.com/pipermail/vtkusers/attachments/20160626/203154f2/attachment-0001.html>


More information about the vtkusers mailing list