Home > Software engineering >  QtWidget is not showing
QtWidget is not showing

Time:12-16

I am trying to use PyQt5 to show two widgets, the first one is a plot of sin, cos and tan function. I am using the pyqtgraph and used the code that was found in the answer of this question. I am also using another widget that draws a cube using PyOpenGL, by taking the example found in this link. I am trying to show this two widgets in one main widget, which is the main window. My approach is the following

  1. Take a main widget.
  2. In the main widget, use a QVBoxLayout()
  3. In the QVBoxLayout, at two widgets mentioned above

But when I am running the code, only the plot that is using the pyqtgraph is shown but not the cube that is drawn using PyOpenGL. After a little bit debugging, I was able to find out that the height of the cube widget is setting to 0 by default. I am not sure why this is hapenning. I tried calling glWidget.resize(640,480). But it didn't work. I am new on working with PyQt and PyOpenGL. I think I am missing some details that will allow the height of the glWidget to be greater than 0, if my assumption is correct. Also I am not sure if this is actually possible to do. My current code is given below, it is a little bit messy.

import sys
from OpenGL.GL.images import asWrapper

from PyQt5.QtWidgets import QApplication, QGridLayout
from PyQt5 import QtWidgets
import pyqtgraph as pg
from OpenGL.GL import *
from OpenGL.GLU import *
from PyQt5 import QtGui
from PyQt5.QtOpenGL import *
from PyQt5 import QtCore, QtWidgets
import pyqtgraph as pg
import numpy as np
from PyQt5 import QtOpenGL
import OpenGL.GL as gl
from OpenGL import GLU  
from OpenGL.arrays import vbo

class TimeLine(QtCore.QObject):
    frameChanged = QtCore.pyqtSignal(int)

    def __init__(self, interval=60, loopCount=1, parent=None):
        super(TimeLine, self).__init__(parent)
        self._startFrame = 0
        self._endFrame = 0
        self._loopCount = loopCount
        self._timer = QtCore.QTimer(self, timeout=self.on_timeout)
        self._counter = 0
        self._loop_counter = 0
        self.setInterval(interval)

    def on_timeout(self):
        if self._startFrame <= self._counter < self._endFrame:
            self.frameChanged.emit(self._counter)
            self._counter  = 1
        else:
            self._counter = 0
            self._loop_counter  = 1
        
        if self._loopCount > 0:
            if self._loop_counter >= self.loopCount():
                self._timer.stop()
    
    def setLoopCount(self, loopCount):
        self._loopCount = loopCount
    
    def loopCount(self):
        return self._loopCounts
    interval = QtCore.pyqtProperty(int, fget=loopCount, fset=setLoopCount)

    def setInterval(self, interval):
        self._timer.setInterval(interval)
    
    def interval(self):
        return self._timer.interval()

    interval = QtCore.pyqtProperty(int, fget=interval, fset=setInterval)

    def setFrameRange(self, startFrame, endFrame):
        self._startFrame = startFrame
        self._endFrame = endFrame

    @QtCore.pyqtSlot()
    def start(self):
        self._counter = 0
        self._loop_counter = 0
        self._timer.start()

class GLWidget(QtOpenGL.QGLWidget):
    def __init__(self, parent = None):
        self.parent = parent
        QtOpenGL.QGLWidget.__init__(self, parent)
        self.resizeGL(640,800)
    
    def initializeGL(self):
        self.qglClearColor(QtGui.QColor(0,0,255))
        gl.glEnable(gl.GL_DEPTH_TEST)

        self.initGeometry()

        self.rotX = 0.0
        self.rotY = 0.0
        self.rotZ = 0.0

    def resizeGL(self, width, height):
        gl.glViewport(0, 0, width, height)
        gl.glMatrixMode(gl.GL_PROJECTION)
        gl.glLoadIdentity()
        print(width, height)
        aspect = width / float(height)

        GLU.gluPerspective(45.0, aspect, 1.0, 100.0)
        gl.glMatrixMode(gl.GL_MODELVIEW)

    def paintGL(self):
        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
        gl.glPushMatrix()

        gl.glTranslate(0.0, 0.0, -50.0)
        gl.glScale(20.0, 20.0, 20.0)
        gl.glRotate(self.rotX, 1.0, 0.0, 0.0)
        gl.glRotate(self.rotY, 0.0, 1.0, 0.0)
        gl.glRotate(self.rotZ, 0.0, 0.0, 1.0)
        gl.glTranslate(-0.5, -0.5, -0.5)

        gl.glEnableClientState(gl.GL_VERTEX_ARRAY)
        gl.glEnableClientState(gl.GL_COLOR_ARRAY)

        gl.glVertexPointer(3, gl.GL_FLOAT, 0, self.vertVBO)
        gl.glColorPointer(3, gl.GL_FLOAT, 0, self.colorVBO)

        gl.glDrawElements(gl.GL_QUADS, len(self.cubeIdxArray), gl.GL_UNSIGNED_INT, self.cubeIdxArray)

        gl.glDisableClientState(gl.GL_VERTEX_ARRAY)
        gl.glDisableClientState(gl.GL_COLOR_ARRAY)

        gl.glPopMatrix()

    def initGeometry(self):
        self.cubeVtxArray = np.array(
                [[0.0, 0.0, 0.0],
                 [1.0, 0.0, 0.0],
                 [1.0, 1.0, 0.0],
                 [0.0, 1.0, 0.0],
                 [0.0, 0.0, 1.0],
                 [1.0, 0.0, 1.0],
                 [1.0, 1.0, 1.0],
                 [0.0, 1.0, 1.0]])
        self.vertVBO = vbo.VBO(np.reshape(self.cubeVtxArray,
                                          (1, -1)).astype(np.float32))
        self.vertVBO.bind()
        
        self.cubeClrArray = np.array(
                [[0.0, 0.0, 0.0],
                 [1.0, 0.0, 0.0],
                 [1.0, 1.0, 0.0],
                 [0.0, 1.0, 0.0],
                 [0.0, 0.0, 1.0],
                 [1.0, 0.0, 1.0],
                 [1.0, 1.0, 1.0],
                 [0.0, 1.0, 1.0 ]])
        self.colorVBO = vbo.VBO(np.reshape(self.cubeClrArray,
                                           (1, -1)).astype(np.float32))
        self.colorVBO.bind()

        self.cubeIdxArray = np.array(
                [0, 1, 2, 3,
                 3, 2, 6, 7,
                 1, 0, 4, 5,
                 2, 1, 5, 6,
                 0, 3, 7, 4,
                 7, 6, 5, 4 ])

    def setRotX(self, val):
        self.rotX = np.pi * val

    def setRotY(self, val):
        self.rotY = np.pi * val

    def setRotZ(self, val):
        self.rotZ = np.pi * val
    

class MainGui(QtWidgets.QMainWindow):
    def __init__(self):
        QtWidgets.QMainWindow.__init__(self)
        self.resize(600,600)
        self.cube = GLWidget(self)
        self.setupUI()

    def setupUI(self):
        central_widget = QtWidgets.QWidget()
        central_layout = QtWidgets.QVBoxLayout()
        central_widget.setLayout(central_layout)

        self.setCentralWidget(central_widget)
        
        pg.setConfigOption('background',0.95)
        pg.setConfigOptions(antialias=True)
        
        self.plot = pg.PlotWidget()
        self.plot.setAspectLocked(lock = True, ratio = 0.01)
        #self.cube = GLWidget(self)
        #self.cube.resize(200,200)
        
        central_layout.addWidget(self.cube)
        central_layout.addWidget(self.plot)

        self._plots = [self.plot.plot([], [], pen=pg.mkPen(color=color, width=2)) for color in ('g', 'r', 'y')]
        self._timeline = TimeLine(loopCount = 0, interval = 10)
        self._timeline.setFrameRange(0,720)
        self._timeline.frameChanged.connect(self.generate_data)
        self._timeline.start()
    def plot_data(self, data):
        for plt, val in zip(self._plots, data):
            plt.setData(range(len(val)),val)

    @QtCore.pyqtSlot(int)
    def generate_data(self, i):
        ang = np.arange(i, i   720)
        cos_func = np.cos(np.radians(ang))
        sin_func = np.sin(np.radians(ang))
        tan_func = sin_func/cos_func
        tan_func[(tan_func < -3) | (tan_func > 3)] = np.NaN
        self.plot_data([sin_func, cos_func, tan_func])


if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    gui = MainGui()
    gui.show()
    sys.exit(app.exec_())

CodePudding user response:

It seems that QGLWidget (which, by the way, is deprecated, and QOpenGLWidget should be used instead) doesn't implement sizeHint(), so it returns an invalid size (QSize(-1, -1)), which means that the widget can be possibly resized to a 0 width and/or height.

Since the plot widget has an expanding size policy (and dynamically reimplements sizeHint()) the result is that the gl widget is completely hidden, having 0 height.

A possible solution is to add the widgets with a proper stretch argument to the layout.

If you want both widgets to have the same height, you can do the following:

    central_layout.addWidget(self.cube, stretch=1)
    central_layout.addWidget(self.plot, stretch=1)

Note that the stretch is ratio-based (only integers are considered), so, if you want the cube have half the height of the plot:

    central_layout.addWidget(self.cube, stretch=1)
    central_layout.addWidget(self.plot, stretch=2)

Alternatively, you can use setMinimumHeight() for the gl widget, but since the plot has an expanding policy (which normally takes precedence), that gl widget will always have that height. A better solution would be to set an expanding policy for the gl widget and implement QSizeHint, but remember that the plot widget has a dynamic size hint, so it will always take some amount of "size priority".

class GLWidget(QtOpenGL.QGLWidget):
    def __init__(self, parent = None):
        QtOpenGL.QGLWidget.__init__(self, parent)
        self.setSizePolicy(
            QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)

    def sizeHint(self):
        return QtCore.QSize(300, 150)

    # ...

There should be no need to manually call resizeGL() in the __init__, and you should also always use the dynamic access parent() function to get the parent.

  • Related