I use the scroll bar for the chart. When adding a second and subsequent graph, the bars do not scroll through the new graphs.
And when deleting new charts, an error occurs when trying to scroll.
I understand that need to have separate access to each chart, but I don't know how to do it?
I assume that need to have access to "self._chart.axis" to all graphs, via an array.
from PyQt5 import QtCore, QtGui, QtWidgets, QtChart
import math
mas =[1.33, 1.15, 1.55, 1.65, 1.64, 1.91, 1.33, 2.3, 1.5, 1.35, 2.52, 1.77, 1.7, 1.87, 2.0, 1.55, 1.73, 2.1,
1.33, 1.15, 1.55, 1.92, 1.64, 1.91, 1.33, 1.71, 1.5, 1.35, 1.22, 1.77, 1.7, 1.87, 2.7, 1.55, 1.73, 2.1,
1.33, 1.15, 1.55, 1.92, 1.64, 1.91, 1.33, 1.71, 1.5, 1.35, 1.22, 1.77, 1.7, 1.87, 2.0, 1.55, 1.73, 2.1]
x = len(mas)
x_ = x - 1
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, start = 1, parent=None):
self.start = start
super().__init__(parent)
self.step = 30
self.scrollbar = QtWidgets.QScrollBar(
QtCore.Qt.Horizontal,
sliderMoved=self.onAxisSliderMoved,
pageStep=self.step,
)
self.scrollbar.setRange(0, x_)
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
self.open = QtWidgets.QPushButton()
self.open.setText("Open")
self.open.clicked.connect(self.open_clicked)
self.close = QtWidgets.QPushButton()
self.close.setText("Close")
self.close.clicked.connect(self.close_clicked)
self.hbox = QtWidgets.QHBoxLayout()
self.hbox.addWidget(self.open)
self.hbox.addWidget(self.close)
self.splitter = QtWidgets.QSplitter(QtCore.Qt.Vertical)
self.lay = QtWidgets.QVBoxLayout(central_widget)
self.lay.insertLayout(0, self.hbox)
self.lay.addWidget(self.splitter, stretch=1)
self.lay.addWidget(self.scrollbar)
self.open_clicked()
def open_clicked(self):
self._chart_view = QtChart.QChartView()
self._chart = QtChart.QChart()
self._line_serie = QtChart.QLineSeries()
for i in range(0, len(mas)):
self._line_serie.append(QtCore.QPointF(i, mas[i]))
self._chart.addSeries(self._line_serie)
self._chart.createDefaultAxes()
self._chart_view.setChart(self._chart)
self.splitter.addWidget(self._chart_view)
def close_clicked(self):
count = self.splitter.count()
if count > 1:
w = self.splitter.widget(count - 1)
if w is not None:
w.deleteLater()
def adjust_axes(self, value_min, value_max):
if value_min >= 0 and value_max >= 0 and value_max <= x_ and value_max > value_min:
self._chart.axisX(self._line_serie).setRange(value_min, value_max)
#QtCore.pyqtSlot(int)
def onAxisSliderMoved(self, value):
value2 = value + self.step
value1 = value
if value2 >= x_:
value2 = x_
value1 = value2 - self.step
self.adjust_axes(math.floor(value1), math.ceil(value2))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow(start = 0)
w.show()
sys.exit(app.exec_())
The problem is similar to your previous post: If you use the same variable to reference several objects then the variable only refers to the last object, therefore when deleting that object and wanting to delete the returned object the application crashes. The solution is to use the splitter to iterate over the QChartView, and using that information get the QChart and QLineSeries:
def open_clicked(self):
chart_view = QtChart.QChartView()
chart = QtChart.QChart()
line_serie = QtChart.QLineSeries()
for i, value in enumerate(mas):
line_serie.append(QtCore.QPointF(i, value))
chart.addSeries(line_serie)
chart.createDefaultAxes()
chart_view.setChart(chart)
self.splitter.addWidget(chart_view)
def adjust_axes(self, value_min, value_max):
for i in range(self.splitter.count()):
chart_view = self.splitter.widget(i)
if isinstance(chart_view, QtChart.QChartView):
chart = chart_view.chart()
for serie in chart.series():
chart.axisX(serie).setRange(value_min, value_max)
Related
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
Take a main widget.
In the main widget, use a QVBoxLayout()
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_())
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.
Now the application displays two graphs with the same height:
CODE:
import sys
from PyQt5 import QtWidgets, uic
from PyQt5.QtWidgets import QListWidget, QVBoxLayout, QMainWindow
import pyqtgraph as pg
class Draw_interface(QMainWindow):
def __init__(self):
global time_update
super(Draw_interface, self).__init__()
uic.loadUi('charts.ui', self)
# Add two charts
self.my_draw = pg.GraphicsLayoutWidget(show=True)
self.p1 = self.my_draw.addPlot(row=0, col=0, stretch=3)
self.p2 = self.my_draw.addPlot(row=1, col=0, stretch=1)
# Set widget for chart
my_layout = QVBoxLayout()
my_layout.addWidget(self.my_draw)
self.frame_for_charts.setLayout(my_layout)
# Draw charts
y = [2.2, 3.0, 1.3, 2.5, 1.9, 2.2, 5.5, 6.6]
y2 = [2.3, 3.3, 2.8, 2.2, 3.3, 3.1, 2.8, 4.4]
curve1 = self.p1.plot(y)
curve2 = self.p2.plot(y2)
self.show()
my_app = QtWidgets.QApplication([])
my_main_window = Draw_interface()
sys.exit(my_app.exec_())
stretch=3 and stratch=1 - do not lead to the desired effect.
How to set the stretch factor correctly so that the upper graph is 75% of the height, the lower graph is 25% of the height?
And it was like this:
Maybe:
self.my_draw.itemAt(0, 0).setGeometry(x1,y1,x2,y2)
self.my_draw.itemAt(0, 0).updateGeometry()
But this is clearly not the best option.
There is no parameter like "stretch" or "stratch" for addPlot method.
If you want to use GraphicsLayoutWidget, you can add empty labels to take up spaces of the next column to serve as a reference of the grid.(kind of hacky...)
Or you can put 2 plotwidget inside QVBoxLayout and use "stretch" parameter in addWidgets method.
The result using QVBoxLayout is more accurate.
GraphicsLayoutWidget solution.
import sys
from PyQt5 import QtWidgets, QtCore
import pyqtgraph as pg
class Draw_interface(QtWidgets.QMainWindow):
def __init__(self):
super(Draw_interface, self).__init__()
# Add two charts
self.my_draw = pg.GraphicsLayoutWidget(show=True)
self.p1 = self.my_draw.addPlot(row=0, col=0, rowspan = 3)
self.p2 = self.my_draw.addPlot(row=3, col=0)
# Add label with no text to take up spaces of the next column
self.p3 = self.my_draw.addLabel(text='',row=0, col=1)
self.p4 = self.my_draw.addLabel(text='',row=1, col=1)
self.p5 = self.my_draw.addLabel(text='',row=2, col=1)
self.p6 = self.my_draw.addLabel(text='',row=3, col=1)
# Draw charts
y = [2.2, 3.0, 1.3, 2.5, 1.9, 2.2, 5.5, 6.6]
y2 = [2.3, 3.3, 2.8, 2.2, 3.3, 3.1, 2.8, 4.4]
self.p1.plot(y)
self.p2.plot(y2)
self.setCentralWidget(self.my_draw)
def main():
QtWidgets.QApplication.setAttribute(QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
app = QtWidgets.QApplication(sys.argv)
main = Draw_interface()
main.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
QVBoxLayout solution.
import sys
from PyQt5 import QtWidgets, QtCore
import pyqtgraph as pg
class Draw_interface(QtWidgets.QMainWindow):
def __init__(self):
super(Draw_interface, self).__init__()
# Add two charts
p1 = pg.PlotWidget(name = "Plot1")
p2 = pg.PlotWidget(name = "Plot2")
# Draw charts
y = [2.2, 3.0, 1.3, 2.5, 1.9, 2.2, 5.5, 6.6]
y2 = [2.3, 3.3, 2.8, 2.2, 3.3, 3.1, 2.8, 4.4]
p1.plot(y)
p2.plot(y2)
l = QtWidgets.QVBoxLayout()
l.addWidget(p1, stretch=3)
l.addWidget(p2, stretch=1)
w = QtWidgets.QWidget()
w.setLayout(l)
self.setCentralWidget(w)
self.setStyleSheet("QWidget { background-color: black; }")
def main():
QtWidgets.QApplication.setAttribute(QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
app = QtWidgets.QApplication(sys.argv)
main = Draw_interface()
main.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Also see answer here: Unequal sizes for subplots in pyqtgraph
Not sure if this is meant to be part of pyqtgraph's public API, but I was able to get row stretching of items in a pg.GraphicsLayoutWidget with the following:
The following result gave plot0 to be twice as large as plot1.
graphics_layout = pg.GraphicsLayoutWidget()
plot0 = graphics_layout.addPlot(row=0, col=0)
plot1 = graphics_layout.addPlot(row=1, col=0)
graphics_layout.ci.layout.setRowStretchFactor(0, 2) # row 0, stretch factor 2
graphics_layout.ci.layout.setRowStretchFactor(1, 1) # row 1, stretch factor 1
I am stuck trying to change data in a VBO.
I setup a scene with 2 Triangle primitives using a VBO via the python OpenGL.arrays.vbo helper class. That worked.
Then I want to change the data (in the minimal example below just shift one vertex when a button is clicked) which I cannot bring to work. I'm not sure if I use the VBO incorrectly or if there is some triviality blocking the redraw on the PyQt5 side.
Below is the full minimal example, the important stuff takes play in the member functions initializeGL, paintGL, and shift.
Inside GLWidget.shift I tried different approaches following the docs and this answer without success. Any help is appreciated.
#!/usr/bin/env python
import ctypes
import sys
import numpy as np
import OpenGL.arrays.vbo as glvbo
from PyQt5.QtCore import QSize
from PyQt5.QtWidgets import (QApplication, QHBoxLayout, QOpenGLWidget,
QWidget, QPushButton)
import OpenGL.GL as gl
class Window(QWidget):
def __init__(self):
super(Window, self).__init__()
self.glWidget = GLWidget()
button = QPushButton('shift', self)
button.clicked.connect(self.glWidget.shift)
layout = QHBoxLayout()
layout.addWidget(self.glWidget)
layout.addWidget(button)
self.setLayout(layout)
class GLWidget(QOpenGLWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.object = None
def minimumSizeHint(self):
return QSize(400, 400)
def initializeGL(self):
gl.glClearColor(0., 0., 0., 0.)
# a red and a green triangle
self.vertices = np.array([
# <- x,y,z -----> <- r,g,b -->
-0.5, -0.2, 0.0, 1.0, 0.0, 0.0,
0.5, -0.5, 0.0, 1.0, 0.0, 0.0,
0.5, 0.5, 0.0, 1.0, 0.0, 0.0,
0.4, -0.2, 0.0, 0.0, 1.0, 0.0,
1.4, -0.5, 0.0, 0.0, 1.0, 0.0,
1.4, 0.5, 0.0, 0.0, 1.0, 0.0,
], 'f')
self.vbo = glvbo.VBO(self.vertices)
self.vbo.bind()
self.object = gl.glGenLists(1)
gl.glNewList(self.object, gl.GL_COMPILE)
gl.glEnableClientState(gl.GL_VERTEX_ARRAY)
gl.glEnableClientState(gl.GL_COLOR_ARRAY)
buffer_offset = ctypes.c_void_p
stride = (3+3)*self.vertices.itemsize
gl.glVertexPointer(3, gl.GL_FLOAT, stride, None)
gl.glColorPointer(3, gl.GL_FLOAT, stride, buffer_offset(12))
gl.glDrawArrays(gl.GL_TRIANGLES, 0, 6)
gl.glDisableClientState(gl.GL_VERTEX_ARRAY)
gl.glDisableClientState(gl.GL_COLOR_ARRAY)
gl.glEndList()
gl.glShadeModel(gl.GL_FLAT)
def paintGL(self):
gl.glClear(
gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
gl.glLoadIdentity()
gl.glRotated(50.0, 0.0, 1.0, 0.0)
gl.glCallList(self.object)
def resizeGL(self, width, height):
side = min(width, height)
if side < 0:
return
gl.glViewport((width - side) // 2, (height - side) // 2, side,
side)
gl.glMatrixMode(gl.GL_PROJECTION)
gl.glLoadIdentity()
gl.glOrtho(-1., +1., -1., +1., -100.0, 100.0)
gl.glMatrixMode(gl.GL_MODELVIEW)
def shift(self):
# shift y-position of one vertex
self.vertices[1] += 10.3
assert self.vertices is self.vbo.data
# version 1
# self.vbo.implementation.glBufferSubData(self.vbo.target, 0, self.vbo.data)
# version 2
# self.vbo[:] = self.vertices[:]
# self.vbo.bind()
# self.vbo.copy_data()
# version 2b (use slice)
# self.vbo[1:2] = self.vertices[1:2]
# self.vbo.bind()
# self.vbo.copy_data()
# version 3
self.vbo.set_array(self.vertices)
self.vbo.bind()
self.vbo.copy_data()
self.update()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
The code runs on an Ubuntu 18.04 machine under python 3.6 with
Vendor: Intel Open Source Technology Center
Renderer: Mesa DRI Intel(R) HD Graphics 5500 (Broadwell GT2)
OpenGL Version: 3.0 Mesa 19.2.8
Shader Version: 1.30
Sisplay lists (glGenList) are deprecated. What you try to encode in the list is the Vertex Specification.
I recommend to use a Vertex Array Object instead.
Create the VAO, before specifying the array of generic vertex attribute data:
class GLWidget(QOpenGLWidget):
# [...]
def initializeGL(self):
# [...]
self.vbo = glvbo.VBO(self.vertices)
self.vbo.bind()
self.vao = gl.glGenVertexArrays(1)
gl.glBindVertexArray(self.vao)
gl.glEnableClientState(gl.GL_VERTEX_ARRAY)
gl.glEnableClientState(gl.GL_COLOR_ARRAY)
buffer_offset = ctypes.c_void_p
stride = (3+3)*self.vertices.itemsize
gl.glVertexPointer(3, gl.GL_FLOAT, stride, None)
gl.glColorPointer(3, gl.GL_FLOAT, stride, buffer_offset(12))
gl.glBindVertexArray(0)
When you want to draw the object, then is sufficient to bind the VAO:
class GLWidget(QOpenGLWidget):
# [...]
def paintGL(self):
gl.glClear(
gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
gl.glLoadIdentity()
gl.glRotated(50.0, 0.0, 1.0, 0.0)
gl.glBindVertexArray(self.vao)
gl.glDrawArrays(gl.GL_TRIANGLES, 0, 6)
gl.glBindVertexArray(0)
Note, the display list does not work, because certain commands are not compiled into the display list but are executed immediately, including glVertexPointer and glColorPointer. See glNewList.
I'm diving into Qt3D framework and have decided to replicate a simplified version of this c++ example
Unfortunately, I don't see a torus mesh on application start.
I've created all required entities and enabled a mesh in SceneModifier class.
What could be a problem with it? I thought that I've had a bad camera implementation, but it seems ok. Same with point light.
import sys
from PySide2 import QtWidgets, QtCore, QtGui
from PySide2.Qt3DCore import Qt3DCore
from PySide2.Qt3DExtras import Qt3DExtras
from PySide2.Qt3DRender import Qt3DRender
class SceneModifier(QtCore.QObject):
def __init__(self, root_entity=None):
super().__init__()
# Scene entity
self._m_root_entity = Qt3DCore.QEntity(root_entity)
# Torus shape data
self.m_torus = Qt3DExtras.QTorusMesh()
self.m_torus.setRadius(1.0)
self.m_torus.setMinorRadius(0.4)
self.m_torus.setRings(100)
self.m_torus.setSlices(20)
# Torus transform
torus_transform = Qt3DCore.QTransform()
torus_transform.setScale(2.0)
torus_transform.setRotation(QtGui.QQuaternion.fromAxisAndAngle(QtGui.QVector3D(0.0, 0.1, 0.0), 25.0))
torus_transform.setTranslation(QtGui.QVector3D(0.0, 0.0, 0.0))
# Torus material
torus_mat = Qt3DExtras.QPhongMaterial()
torus_mat.setDiffuse(QtGui.QColor(255, 102, 0))
# Torus mesh
self.m_torus_entity = Qt3DCore.QEntity(self._m_root_entity)
self.m_torus_entity.addComponent(self.m_torus)
self.m_torus_entity.addComponent(torus_mat)
self.m_torus_entity.addComponent(torus_transform)
self.m_torus_entity.setEnabled(True)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
view = Qt3DExtras.Qt3DWindow()
view.defaultFrameGraph().setClearColor(QtGui.QColor(89, 89, 89))
container = QtWidgets.QWidget.createWindowContainer(view)
screen_size = QtCore.QSize(view.screen().size())
container.setMinimumSize(QtCore.QSize(720, 680))
container.setMaximumSize(QtCore.QSize(screen_size))
root_entity = Qt3DCore.QEntity()
camera_entity = Qt3DRender.QCamera(view.camera())
camera_entity.lens().setPerspectiveProjection(45.0, 16.0/9.0, 0.1, 1000.0)
camera_entity.setPosition(QtGui.QVector3D(0, 0, 20.0))
camera_entity.setUpVector(QtGui.QVector3D(0, 1, 0))
camera_entity.setViewCenter(QtGui.QVector3D(0, 0, 0))
light_entity = Qt3DCore.QEntity(root_entity)
point_light = Qt3DRender.QPointLight(light_entity)
point_light.setColor("white")
point_light.setIntensity(1)
light_entity.addComponent(point_light)
light_transform = Qt3DCore.QTransform(light_entity)
light_transform.setTranslation(camera_entity.position())
light_entity.addComponent(light_transform)
cam_control = Qt3DExtras.QFirstPersonCameraController(root_entity)
cam_control.setCamera(camera_entity)
modifier = SceneModifier(root_entity=root_entity)
view.setRootEntity(root_entity)
widget = QtWidgets.QWidget()
h_layout = QtWidgets.QHBoxLayout()
h_layout.addWidget(container)
widget.setLayout(h_layout)
widget.show()
sys.exit(app.exec_())
I have implemented the translation of example Qt 3D: Basic Shapes C++ Example into PySide2:
import sys
from PySide2 import QtWidgets, QtCore, QtGui
from PySide2.Qt3DCore import Qt3DCore
from PySide2.Qt3DExtras import Qt3DExtras
from PySide2.Qt3DRender import Qt3DRender
from PySide2.Qt3DInput import Qt3DInput
class SceneModifier(QtCore.QObject):
def __init__(self, root_entity=None):
super().__init__()
self.m_rootEntity = root_entity
self.m_torus = Qt3DExtras.QTorusMesh(
radius=1.0, minorRadius=0.4, rings=100, slices=20
)
self.torusTransform = Qt3DCore.QTransform(
scale=2.0,
rotation=QtGui.QQuaternion.fromAxisAndAngle(
QtGui.QVector3D(0.0, 1.0, 0.0), 25.0
),
translation=QtGui.QVector3D(5.0, 4.0, 0.0),
)
self.torusMaterial = Qt3DExtras.QPhongMaterial(diffuse=QtGui.QColor("#beb32b"))
self.m_torusEntity = Qt3DCore.QEntity(self.m_rootEntity)
self.m_torusEntity.addComponent(self.m_torus)
self.m_torusEntity.addComponent(self.torusMaterial)
self.m_torusEntity.addComponent(self.torusTransform)
self.cone = Qt3DExtras.QConeMesh(
topRadius=0.5, bottomRadius=1, length=3, rings=50, slices=20
)
self.coneTransform = Qt3DCore.QTransform(
scale=1.5,
rotation=QtGui.QQuaternion.fromAxisAndAngle(
QtGui.QVector3D(1.0, 4.0, -1.5), 45.0
),
translation=QtGui.QVector3D(0.0, 4.0, -1.5),
)
self.coneMaterial = Qt3DExtras.QPhongMaterial(diffuse=QtGui.QColor("#928327"))
self.m_coneEntity = Qt3DCore.QEntity(self.m_rootEntity)
self.m_coneEntity.addComponent(self.cone)
self.m_coneEntity.addComponent(self.coneMaterial)
self.m_coneEntity.addComponent(self.coneTransform)
self.cylinder = Qt3DExtras.QCylinderMesh(
radius=1, length=3, rings=100, slices=20
)
self.cylinderTransform = Qt3DCore.QTransform(
scale=1.5,
rotation=QtGui.QQuaternion.fromAxisAndAngle(
QtGui.QVector3D(1.0, 0.0, 0.0), 45.0
),
translation=QtGui.QVector3D(-5.0, 4.0, -1.5),
)
self.cylinderMaterial = Qt3DExtras.QPhongMaterial(
diffuse=QtGui.QColor("#928327")
)
self.m_cylinderEntity = Qt3DCore.QEntity(self.m_rootEntity)
self.m_cylinderEntity.addComponent(self.cylinder)
self.m_cylinderEntity.addComponent(self.cylinderMaterial)
self.m_cylinderEntity.addComponent(self.cylinderTransform)
self.cuboid = Qt3DExtras.QCuboidMesh()
self.cuboidTransform = Qt3DCore.QTransform(
scale=4.0, translation=QtGui.QVector3D(5.0, -4.0, 0.0),
)
self.cuboidMaterial = Qt3DExtras.QPhongMaterial(diffuse=QtGui.QColor("#665423"))
self.m_cuboidEntity = Qt3DCore.QEntity(self.m_rootEntity)
self.m_cuboidEntity.addComponent(self.cuboid)
self.m_cuboidEntity.addComponent(self.cuboidMaterial)
self.m_cuboidEntity.addComponent(self.cuboidTransform)
self.planeMesh = Qt3DExtras.QPlaneMesh(width=2, height=2)
self.planeTransform = Qt3DCore.QTransform(
scale=1.3,
rotation=QtGui.QQuaternion.fromAxisAndAngle(
QtGui.QVector3D(1.0, 0.0, 0.0), 45.0
),
translation=QtGui.QVector3D(0.0, -4.0, 0.0),
)
self.planeMaterial = Qt3DExtras.QPhongMaterial(diffuse=QtGui.QColor("#a69929"))
self.m_planeEntity = Qt3DCore.QEntity(self.m_rootEntity)
self.m_planeEntity.addComponent(self.planeMesh)
self.m_planeEntity.addComponent(self.planeMaterial)
self.m_planeEntity.addComponent(self.planeTransform)
self.sphereMesh = Qt3DExtras.QSphereMesh(rings=20, slices=20, radius=2)
self.sphereTransform = Qt3DCore.QTransform(
scale=1.3, translation=QtGui.QVector3D(-5.0, -4.0, 0.0),
)
self.sphereMaterial = Qt3DExtras.QPhongMaterial(diffuse=QtGui.QColor("#a69929"))
self.m_sphereEntity = Qt3DCore.QEntity(self.m_rootEntity)
self.m_sphereEntity.addComponent(self.sphereMesh)
self.m_sphereEntity.addComponent(self.sphereMaterial)
self.m_sphereEntity.addComponent(self.sphereTransform)
#QtCore.Slot(bool)
def enableTorus(self, enabled):
self.m_torusEntity.setEnabled(enabled)
#QtCore.Slot(bool)
def enableCone(self, enabled):
self.m_coneEntity.setEnabled(enabled)
#QtCore.Slot(bool)
def enableCylinder(self, enabled):
self.m_cylinderEntity.setEnabled(enabled)
#QtCore.Slot(bool)
def enableCuboid(self, enabled):
self.m_cuboidEntity.setEnabled(enabled)
#QtCore.Slot(bool)
def enablePlane(self, enabled):
self.m_planeEntity.setEnabled(enabled)
#QtCore.Slot(bool)
def enableSphere(self, enabled):
self.m_sphereEntity.setEnabled(enabled)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
view = Qt3DExtras.Qt3DWindow()
view.defaultFrameGraph().setClearColor(QtGui.QColor("#4d4d4f"))
container = QtWidgets.QWidget.createWindowContainer(view)
screenSize = view.screen().size()
container.setMinimumSize(QtCore.QSize(200, 100))
container.setMaximumSize(screenSize)
widget = QtWidgets.QWidget()
hLayout = QtWidgets.QHBoxLayout(widget)
vLayout = QtWidgets.QVBoxLayout()
vLayout.setAlignment(QtCore.Qt.AlignTop)
hLayout.addWidget(container, 1)
hLayout.addLayout(vLayout)
widget.setWindowTitle("Basic shapes")
input_ = Qt3DInput.QInputAspect()
view.registerAspect(input_)
rootEntity = Qt3DCore.QEntity()
cameraEntity = view.camera()
cameraEntity.lens().setPerspectiveProjection(45.0, 16.0 / 9.0, 0.1, 1000.0)
cameraEntity.setPosition(QtGui.QVector3D(0, 0, 20.0))
cameraEntity.setUpVector(QtGui.QVector3D(0, 1, 0))
cameraEntity.setViewCenter(QtGui.QVector3D(0, 0, 0))
lightEntity = Qt3DCore.QEntity(rootEntity)
light = Qt3DRender.QPointLight(lightEntity)
light.setColor("white")
light.setIntensity(1)
lightEntity.addComponent(light)
lightTransform = Qt3DCore.QTransform(lightEntity)
lightTransform.setTranslation(cameraEntity.position())
lightEntity.addComponent(lightTransform)
camController = Qt3DExtras.QFirstPersonCameraController(rootEntity)
camController.setCamera(cameraEntity)
modifier = SceneModifier(rootEntity)
view.setRootEntity(rootEntity)
info = QtWidgets.QCommandLinkButton()
info.setText("Qt3D ready-made meshes")
info.setDescription(
"Qt3D provides several ready-made meshes, like torus, cylinder, cone, cube, plane and sphere."
)
info.setIconSize(QtCore.QSize(0, 0))
torusCB = QtWidgets.QCheckBox(widget)
torusCB.setChecked(True)
torusCB.setText("Torus")
coneCB = QtWidgets.QCheckBox(widget)
coneCB.setChecked(True)
coneCB.setText("Cone")
cylinderCB = QtWidgets.QCheckBox(widget)
cylinderCB.setChecked(True)
cylinderCB.setText("Cylinder")
cuboidCB = QtWidgets.QCheckBox(widget)
cuboidCB.setChecked(True)
cuboidCB.setText("Cuboid")
planeCB = QtWidgets.QCheckBox(widget)
planeCB.setChecked(True)
planeCB.setText("Plane")
sphereCB = QtWidgets.QCheckBox(widget)
sphereCB.setChecked(True)
sphereCB.setText("Sphere")
vLayout.addWidget(info)
vLayout.addWidget(torusCB)
vLayout.addWidget(coneCB)
vLayout.addWidget(cylinderCB)
vLayout.addWidget(cuboidCB)
vLayout.addWidget(planeCB)
vLayout.addWidget(sphereCB)
torusCB.stateChanged.connect(modifier.enableTorus)
coneCB.stateChanged.connect(modifier.enableCone)
cylinderCB.stateChanged.connect(modifier.enableCylinder)
cuboidCB.stateChanged.connect(modifier.enableCuboid)
planeCB.stateChanged.connect(modifier.enablePlane)
sphereCB.stateChanged.connect(modifier.enableSphere)
torusCB.setChecked(True)
coneCB.setChecked(True)
cylinderCB.setChecked(True)
cuboidCB.setChecked(True)
planeCB.setChecked(True)
sphereCB.setChecked(True)
widget.show()
widget.resize(1200, 800)
sys.exit(app.exec_())
I was wondering how do you add text inside QGraphicsPolygonItem ?
In my script , I use QPolygonF and setPolygon for drawing items and I was wondering if you can insert text inside it ?
I was doing some research, but all I could find is that some people use QGraphicsTextItem instead of polygon item or using QPainter which I don't know how to combine with my QPolygonF. Can somebody advice me how to solve my problem ?
Edit:
Sorry for not providing any example. Here is example of polygon item -
from PySide2.QtGui import QColor, QPolygonF, QPen, QBrush
from PySide2.QtCore import Qt, QPointF, QPoint
from PySide2.QtWidgets import QDialog, QVBoxLayout, QGraphicsView, QGraphicsScene, QGraphicsPolygonItem, QApplication, \
QFrame, QSizePolicy
points_list = [[60.1, 19.6, 0.0], [60.1, 6.5, 0.0], [60.1, -6.5, 0.0], [60.1, -19.6, 0.0], [60.1, -19.6, 0.0],
[20.0, -19.6, 0.0], [-20, -19.6, 0.0], [-60.1, -19.6, 0.0], [-60.1, -19.6, 0.0], [-60.1, -6.5, 0.0],
[-60.1, 6.5, 0.0], [-60.1, 19.6, 0.0], [-60.1, 19.6, 0.0], [-20.0, 19.6, 0.0], [20.0, 19.6, 0.0],
[60.1, 19.6, 0.0]]
class MainWindow(QDialog):
def __init__(self, parent=None):
QDialog.__init__(self, parent=parent)
self.create()
def create(self, **kwargs):
main_layout = QVBoxLayout()
graphics = MainGraphicsWidget()
main_layout.addWidget(graphics)
self.setLayout(main_layout)
class MainGraphicsWidget(QGraphicsView):
def __init__(self, parent=None):
super(MainGraphicsWidget, self).__init__(parent)
self._scene = QGraphicsScene(backgroundBrush=Qt.gray)
self.setScene(self._scene)
self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setBackgroundBrush(QBrush(QColor(30, 30, 30)))
self.setFrameShape(QFrame.NoFrame)
self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding))
self.testButton = GraphicsButton()
self._scene.addItem(self.testButton)
class GraphicsButton(QGraphicsPolygonItem):
def __init__(self, parent=None):
super(GraphicsButton, self).__init__(parent)
self.myPolygon = QPolygonF([QPointF(v1, v2) for v1, v2, v3 in points_list])
self.setPen(QPen(QColor(0, 0, 0), 0, Qt.SolidLine, Qt.FlatCap, Qt.MiterJoin))
self.setPolygon(self.myPolygon)
self.setBrush(QColor(220, 40, 30))
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = MainWindow()
window.setGeometry(500, 100, 500, 900)
window.show()
sys.exit(app.exec_())
So here should be red square like item in center and does anybody know how to put some text inside ?
Here is screenshot of shapes and text which I would like to get:
The simplest solution is to add a QGraphicsSimpleTextItem that is a children of the graphics item.
For simple shapes as rectangles and regular polygons you can then place the item at the center of the item. Remember that the text will not consider the parent item shape, and you'll have to take care of it in some way; this means that you have to consider the text width and height, and parent item shape (that's true for irregular shapes, but also for triangles and rotated squares).
class GraphicsButton(QGraphicsPolygonItem):
def __init__(self, parent=None):
# ...
self.textItem = QGraphicsSimpleTextItem('I am a very large rectangle', self)
rect = self.textItem.boundingRect()
rect.moveCenter(self.boundingRect().center())
self.textItem.setPos(rect.topLeft())
As you can see, the result is outside the parent boundaries:
A possible alternative is to use the QGraphicsTextItem and set its textWidth:
self.textItem = QGraphicsTextItem(self)
self.textItem.setHtml('<center>I am a very large rectangle</center>')
self.textItem.setTextWidth(self.boundingRect().width())
rect = self.textItem.boundingRect()
rect.moveCenter(self.boundingRect().center())
self.textItem.setPos(rect.topLeft())
Note that, opposite to the QGraphicsSimpleTextItem (which uses the default black color for painting), QGraphicsTextItem uses the current palette WindowText role of the widget, inherited from the application, from the view, or from any of the view's parent that has previously set it (once the default text color is set, it won't be changed even if the palette has changed).