I am wanting to set up several 3D mathematical projects with python. The best way I can see to render these is with PyOpenGL. I also want to run it in PyQt5, so that I can have GUI’s along side the render. All the information I can find is either using PyGame or QtDesigner. I would like to work without QtDesigner. Does anyone know where I could find a tutorial on how to set this up?
EDIT:
I managed to get some web scrounging done. I found the following code at https://pythonprogramming.net/community/37/Cube%20rotation%20with%20pyopengl%20and%20pyqt/ where the author asks for help regarding it not running. he says the following about it:
I'm very new to python. I have a problem with my code, it's a very simple rotating cube. I don't have any problem with this cube code with pygame screen but when I use it with pyqt (or Qt designer widgets), it runs but it shows nothing!!!
I copied his code into my IDe, then saved it as cubes.py. I opened up a CMD instance in thae directory of the file and called it. It opened a tiny black Qt window in the middle of the screen:
When I try to resize the window by dragging the corner, it throws a deep traceback:
File "C:\Users\aweso\Documents\Python\cubes\cubes.py", line 1, in <module>
from OpenGL.GL import *
ModuleNotFoundError: No module named 'OpenGL'
C:\Users\aweso\Documents\Python\cubes>cubes.py
Traceback (most recent call last):
File "C:\Users\aweso\Documents\Python\cubes\cubes.py", line 56, in paintGL
glEnd()
File "C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python37_64\lib\site-packages\OpenGL\latebind.py", line 63, in __call__
return self.wrapperFunction( self.baseFunction, *args, **named )
File "C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python37_64\lib\site-packages\OpenGL\GL\exceptional.py", line 45, in glEnd
return baseFunction( )
File "C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python37_64\lib\site-packages\OpenGL\platform\baseplatform.py", line 415, in __call__
return self( *args, **named )
File "C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python37_64\lib\site-packages\OpenGL\error.py", line 234, in glCheckError
baseOperation = baseOperation,
OpenGL.error.GLError: GLError(
err = 1280,
description = b'invalid enumerant',
baseOperation = glEnd,
cArguments = ()
)
Here is his code, unmodified:
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL import *
from PyQt5.QtOpenGL import *
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import *
import sys,time
class MainWindow(QGLWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.widget = glWidget(self)
mainLayout = QVBoxLayout()
mainLayout.addWidget(self.widget)
self.setLayout(mainLayout)
class glWidget(QGLWidget):
def __init__(self, parent):
QGLWidget.__init__(self, parent)
#self.setMinimumSize(400, 400)
self.verticies = (
(1,-1,-1),
(1,1,-1),
(-1,1,-1),
(-1,-1,-1),
(1,-1,1),
(1,1,1),
(-1,-1,1),
(-1,1,1))
self.edges = (
(0,1),
(0,3),
(0,4),
(2,1),
(2,3),
(2,7),
(6,3),
(6,4),
(6,7),
(5,1),
(5,4),
(5,7))
def paintGL(self):
while True:
#glRotatef(1,3,1,1)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glBegin(GL_LINE)
for self.edge in self.edges:
for self.vertex in self.edge:
glVertex3fv(self.verticies[self.vertex])
glEnd()
glFlush()
time.sleep(1)
def resizeGL(self, w, h):
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(-50, 50, -50, 50, -50.0, 50.0)
glViewport(0, 0, w, h)
def initializeGL(self):
#glClearColor(0.0, 0.0, 0.0, 1.0)
gluPerspective(45,800/600,0.1,50.0)
glTranslatef(0.0,0.0,-5)
glRotatef(0,0,0,0)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
I once developed an interactive 3D program using PyOpenGL and PyQt5.
Here's the spike code of that time.
I wish this could be helpful to you.
import sys
import math
from array import array
from OpenGL import GL
from PyQt5.QtCore import pyqtSignal, QPoint, QSize, Qt
from PyQt5.QtGui import QColor, QImage
from PyQt5.QtWidgets import (QApplication, QHBoxLayout, QWidget)
from PyQt5.QtOpenGL import QGLWidget
from shader import ShaderProgram
class Window (QWidget):
def __init__(self):
super(Window, self).__init__()
self.glWidget = GLWidget()
mainLayout = QHBoxLayout()
mainLayout.addWidget(self.glWidget)
self.setLayout(mainLayout)
self.setWindowTitle('Hello')
class GLWidget (QGLWidget):
def __init__(self, parent=None):
super(GLWidget, self).__init__(parent)
def sizeHint(self):
return QSize(1200, 1000)
def initializeGL(self):
GL.glClearColor(0.0, 0.0, 1.0, 0.0) # it's also possible.
GL.glEnable(GL.GL_DEPTH_TEST)
# GL.glEnable(GL.GL_VERTEX_ARRAY)
self._createVertexBuffer()
self.program = ShaderProgram('hello.vert', 'hello.frag')
self.program.use()
self.frontTexture = self._createTexture('tex.png')
self.backTexture = self._createTexture('back-tex.jpg')
# set texture units
GL.glActiveTexture(GL.GL_TEXTURE0)
GL.glBindTexture(GL.GL_TEXTURE_2D, self.frontTexture)
GL.glUniform1i(GL.glGetUniformLocation(self.program.getProgram(), b'frontTexture'), 0)
GL.glActiveTexture(GL.GL_TEXTURE1)
GL.glBindTexture(GL.GL_TEXTURE_2D, self.backTexture)
GL.glUniform1i(GL.glGetUniformLocation(self.program.getProgram(), b'backTexture'), 1)
def paintGL(self):
GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)
self._draw()
def resizeGL(self, width, height):
side = min(width, height)
if side < 0:
return
GL.glViewport((width - side) // 2, (height - side) // 2, side, side)
def _createTexture(self, texFilePath):
qImage = QImage(texFilePath)
texture = QGLWidget.bindTexture(self, qImage, GL.GL_TEXTURE_2D, GL.GL_RGBA)
GL.glGenerateMipmap(GL.GL_TEXTURE_2D)
return texture
def _createVertexBuffer(self):
vertices = array('f', [-1.0, -1.0, 0.0, 1.0, -1.0, 0.0, 1.0, 1.0, 0.0, -1.0, 1.0, 0.0]).tobytes()
colors = array('f', [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0]).tobytes()
indices = [0, 1, 2, 0, 3, 2]
texCoords = array('f', [0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0]).tobytes()
self.vertices = vertices
self.colors = colors
self.indices = indices
self.texCoords = texCoords
def _draw(self):
GL.glEnableVertexAttribArray(0)
GL.glEnableVertexAttribArray(1)
GL.glEnableVertexAttribArray(2)
GL.glVertexAttribPointer(0, 3, GL.GL_FLOAT, GL.GL_FALSE, 0, self.vertices)
GL.glVertexAttribPointer(1, 3, GL.GL_FLOAT, GL.GL_FALSE, 0, self.colors)
GL.glVertexAttribPointer(2, 2, GL.GL_FLOAT, GL.GL_FALSE, 0, self.texCoords)
GL.glDrawElements(GL.GL_TRIANGLES, 6, GL.GL_UNSIGNED_INT, self.indices)
GL.glDisableVertexAttribArray(0)
GL.glDisableVertexAttribArray(1)
GL.glDisableVertexAttribArray(2)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
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.
I found this nice tutorial of drawing and rotating a cube with PyQt and modern OpenGL. My objective was to adapt the script for point clouds, by doing the following (see also code below):
Load point cloud using Open3D and extract coordinates & colors as numpy arrays
Create Vertex Buffer Objects (VBOs) from the arrays
Change the drawing function to gl.glDrawElements(gl.GL_POINTS, ...)
Unfortunately then the point cloud is very distorted and thin (see screenshot). It should actually be a room with chairs and walls.
Do you see if I made a mistake with the VBOs or drawing? Or is there a better way of loading a point cloud?
I tested the example with the old fixed pipeline (glBegin(GL_POINTS) ... glEnd()) and there the point cloud is correctly drawn (but also the performance really bad!).
from PyQt5 import QtCore # core Qt functionality
from PyQt5 import QtGui # extends QtCore with GUI functionality
from PyQt5 import QtOpenGL # provides QGLWidget, a special OpenGL QWidget
from PyQt5 import QtWidgets
import OpenGL.GL as gl # python wrapping of OpenGL
from OpenGL import GLU # OpenGL Utility Library, extends OpenGL functionality
from OpenGL.arrays import vbo
import numpy as np
import open3d as o3d
import sys
# Loading the point cloud from file
def load_pointcloud():
pcd = o3d.io.read_point_cloud("../pointclouds/0004.ply")
print(pcd)
print("Pointcloud Center: " + str(pcd.get_center()))
points = np.asarray(pcd.points)
colors = np.asarray(pcd.colors)
return points, colors
#### here was only the GUI code (slider, ...) , which works fine! ####
class GLWidget(QtOpenGL.QGLWidget):
def __init__(self, parent=None):
self.parent = parent
QtOpenGL.QGLWidget.__init__(self, parent)
def initializeGL(self):
self.qglClearColor(QtGui.QColor(250, 250, 250)) # initialize the screen to blue
gl.glEnable(gl.GL_DEPTH_TEST) # enable depth testing
self.initGeometryPC()
self.rotX = 0.0
self.rotY = 0.0
self.rotZ = 0.0
def setRotX(self, val):
self.rotX = val
def setRotY(self, val):
self.rotY = val
def setRotZ(self, val):
self.rotZ = val
def resizeGL(self, width, height):
gl.glViewport(0, 0, width, height)
gl.glMatrixMode(gl.GL_PROJECTION)
gl.glLoadIdentity()
aspect = width / float(height)
#GLU.gluPerspective(45.0, aspect, 1.0, 100.0) #GLU.gluPerspective(45.0, aspect, 1.0, 100.0)
gl.glOrtho(-2.0, 2.0, -2.0, 2.0, 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() # push the current matrix to the current stack
gl.glTranslate(0.0, 0.0, -5.0) # third, translate cube to specified depth
#gl.glScale(.5, .5, .5) # second, scale point cloud
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) # first, translate point cloud center to origin
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.glPointSize(2)
gl.glDrawElements(gl.GL_POINTS, len(self.pointsIdxArray), gl.GL_UNSIGNED_INT, self.pointsIdxArray)
gl.glDisableClientState(gl.GL_VERTEX_ARRAY)
gl.glDisableClientState(gl.GL_COLOR_ARRAY)
gl.glPopMatrix() # restore the previous modelview matrix
# Push geometric data to GPU
def initGeometryPC(self):
points, colors = load_pointcloud()
self.pointsVtxArray = points
self.vertVBO = vbo.VBO(np.reshape(self.pointsVtxArray, (1, -1)).astype(np.float32))
self.vertVBO.bind()
self.pointsClrArray = colors
self.colorVBO = vbo.VBO(np.reshape(self.pointsClrArray, (1, -1)).astype(np.float32))
self.colorVBO.bind()
self.pointsIdxArray = np.arange(len(points))
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec_())
After a long search I came upon this stackoverflow-post. I adapted my code to that answer by storing point coordinates and colors together in one vbo-object (gl.glGenBuffers(1)). Then I define the vertex and color pointer with the specific stride and offset:
gl.glVertexPointer(3, gl.GL_FLOAT, 6*4, None)
Stride= 24 bytes: [x, y, z, r, g, b] * sizeof(float)
gl.glColorPointer(3, gl.GL_FLOAT, 6*4, ctypes.c_void_p(3*4))
Offset= 12 bytes: the rgb color starts after the 3 coordinates x, y, z
And finally I use gl.glDrawArrays(gl.GL_POINTS, 0, noOfVertices) for drawing the point cloud.
The full code can be seen below (marked with ### NEW ### comments):
from PyQt5 import QtCore # core Qt functionality
from PyQt5 import QtGui # extends QtCore with GUI functionality
from PyQt5 import QtOpenGL # provides QGLWidget, a special OpenGL QWidget
from PyQt5 import QtWidgets
import OpenGL.GL as gl # python wrapping of OpenGL
from OpenGL import GLU # OpenGL Utility Library, extends OpenGL functionality
from OpenGL.arrays import vbo
import numpy as np
import open3d as o3d
import ctypes
import sys # we'll need this later to run our Qt application
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self) # call the init for the parent class
self.resize(300, 300)
self.setWindowTitle('Hello OpenGL App')
self.glWidget = GLWidget(self)
self.initGUI()
timer = QtCore.QTimer(self)
timer.setInterval(20) # period, in milliseconds
timer.timeout.connect(self.glWidget.updateGL)
timer.start()
def initGUI(self):
central_widget = QtWidgets.QWidget()
gui_layout = QtWidgets.QVBoxLayout()
central_widget.setLayout(gui_layout)
self.setCentralWidget(central_widget)
gui_layout.addWidget(self.glWidget)
sliderX = QtWidgets.QSlider(QtCore.Qt.Horizontal)
sliderX.valueChanged.connect(lambda val: self.glWidget.setRotX(val))
sliderY = QtWidgets.QSlider(QtCore.Qt.Horizontal)
sliderY.valueChanged.connect(lambda val: self.glWidget.setRotY(val))
sliderZ = QtWidgets.QSlider(QtCore.Qt.Horizontal)
sliderZ.valueChanged.connect(lambda val: self.glWidget.setRotZ(val))
gui_layout.addWidget(sliderX)
gui_layout.addWidget(sliderY)
gui_layout.addWidget(sliderZ)
class GLWidget(QtOpenGL.QGLWidget):
def __init__(self, parent=None):
self.parent = parent
QtOpenGL.QGLWidget.__init__(self, parent)
def initializeGL(self):
self.qglClearColor(QtGui.QColor(100, 100, 100)) # initialize the screen to blue
gl.glEnable(gl.GL_DEPTH_TEST) # enable depth testing
self.initGeometry()
self.rotX = 0.0
self.rotY = 0.0
self.rotZ = 0.0
def setRotX(self, val):
self.rotX = val
def setRotY(self, val):
self.rotY = val
def setRotZ(self, val):
self.rotZ = val
def resizeGL(self, width, height):
gl.glViewport(0, 0, width, height)
gl.glMatrixMode(gl.GL_PROJECTION)
gl.glLoadIdentity()
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() # push the current matrix to the current stack
gl.glTranslate(0.0, 0.0, -3.0) # third, translate cube to specified depth
#gl.glScale(20.0, 20.0, 20.0) # second, scale cube
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) # first, translate cube center to origin
# Point size
gl.glPointSize(3)
### NEW ###
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.vbo)
stride = 6*4 # (24 bates) : [x, y, z, r, g, b] * sizeof(float)
gl.glEnableClientState(gl.GL_VERTEX_ARRAY)
gl.glVertexPointer(3, gl.GL_FLOAT, stride, None)
gl.glEnableClientState(gl.GL_COLOR_ARRAY)
offset = 3*4 # (12 bytes) : the rgb color starts after the 3 coordinates x, y, z
gl.glColorPointer(3, gl.GL_FLOAT, stride, ctypes.c_void_p(offset))
noOfVertices = self.noPoints
gl.glDrawArrays(gl.GL_POINTS, 0, noOfVertices)
gl.glDisableClientState(gl.GL_VERTEX_ARRAY)
gl.glDisableClientState(gl.GL_COLOR_ARRAY)
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, 0)
### NEW ###
gl.glPopMatrix() # restore the previous modelview matrix
def initGeometry(self):
vArray = self.LoadVertices()
self.noPoints = len(vArray) // 6
print("No. of Points: %s" % self.noPoints)
self.vbo = self.CreateBuffer(vArray)
### NEW ###
def LoadVertices(self):
pcd = o3d.io.read_point_cloud("../pointclouds/0004.ply")
print(pcd)
print("Pointcloud Center: " + str(pcd.get_center()))
points = np.asarray(pcd.points).astype('float32')
colors = np.asarray(pcd.colors).astype('float32')
attributes = np.concatenate((points, colors),axis=1)
print("Attributes shape: " + str(attributes.shape))
return attributes.flatten()
def CreateBuffer(self, attributes):
bufferdata = (ctypes.c_float*len(attributes))(*attributes) # float buffer
buffersize = len(attributes)*4 # buffer size in bytes
vbo = gl.glGenBuffers(1)
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, vbo)
gl.glBufferData(gl.GL_ARRAY_BUFFER, buffersize, bufferdata, gl.GL_STATIC_DRAW)
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, 0)
return vbo
### NEW ###
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec_())
However, I still did not find the correct parameters for initial approach above with two separate VBOs for coordinate and color. So I am happy for further comments.
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 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).
I am developing a software in which I have an OpenGL window. I am creating the GUI of the software using PyQt5 and for the opengGL Window I am using QOpengGLWidget and for object selection, I am using Stencil Buffer and reading STENCIL_INDEX as the following function on mousePressEvent(self, event):
id = glReadPixels(event.x(), self.height - event.y() - 1, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)
but this is not working when I am using with Qt5 GUI and showing following error:
Traceback (most recent call last):
File "/home/akv26/Desktop/internProject/Main/opengl.py", line 92, in mousePressEvent
val = glReadPixels(event.x(), self.height - event.y() - 1, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)
File "/home/akv26/.local/lib/python3.7/site-packages/OpenGL/GL/images.py", line 371, in glReadPixels
imageData
File "/home/akv26/.local/lib/python3.7/site-packages/OpenGL/platform/baseplatform.py", line 402, in __call__
return self( *args, **named )
File "/home/akv26/.local/lib/python3.7/site-packages/OpenGL/error.py", line 232, in glCheckError
baseOperation = baseOperation,
OpenGL.error.GLError: GLError(
err = 1282,
description = b'invalid operation',
baseOperation = glReadPixels,
cArguments = (
183,
228,
1,
1,
GL_DEPTH_COMPONENT,
GL_FLOAT,
array([[0.]], dtype=float32),
)
)
Aborted (core dumped)
and here is the code for my custom QOpenGLWidget:
import sys
from PyQt5.QtWidgets import QOpenGLWidget
from PyQt5.QtCore import QSize, Qt
from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
from math import *
class MyGLWidget(QOpenGLWidget):
def initializeGL(self):
glutInit()
glClearColor(1.0, 0.5, 0.2, 0.5)
glEnable(GL_DEPTH_TEST)
glEnable(GL_LIGHTING)
glEnable(GL_COLOR_MATERIAL)
glEnable(GL_LIGHT0)
glEnable(GL_LIGHT1)
glEnable(GL_NORMALIZE)
glShadeModel(GL_SMOOTH)
def resizeGL(self, w, h):
self.height=h
if h==0:
h=1
ratio = w * 1.0 / h
# Use the Projection Matrix
glMatrixMode(GL_PROJECTION)
# Reset Matrix
glLoadIdentity()
# Set the viewport to be the entire window
glViewport(0, 0, w, h)
# Set the correct perspective.
gluPerspective(45.0, ratio, 0.1, 100.0)
# Get Back to the Modelview
glMatrixMode(GL_MODELVIEW)
def paintGL(self):
glClearStencil(0)
glClear(GL_STENCIL_BUFFER_BIT)
glEnable(GL_STENCIL_TEST)
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE)
ambientColor = [0.2,0.2,0.2,1.0]
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambientColor)
lightColor0 = [0.5,0.5,0.5,1.0]
lightPos0 = [0, 0, -10.0, 1.0]
glLightfv(GL_LIGHT0, GL_DIFFUSE, lightColor0)
glLightfv(GL_LIGHT0, GL_POSITION, lightPos0)
lightColor1 = [0.5, 0.2, 0.2, 1.0]
lightPos1 = [-1.0, 0.5, 0.5, 0.0]
glLightfv(GL_LIGHT1, GL_DIFFUSE, lightColor1)
glLightfv(GL_LIGHT1, GL_POSITION, lightPos1)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
gluLookAt(-5, 5, -5, 0, 0, 0, 0.0, 1.0, 0.0)
self.drawSome()
def drawSome(self):
glStencilFunc(GL_ALWAYS, 1, -1)
glBegin(GL_QUADS)
glColor3f(0.0, 0.5, 0.0)
glNormal3f(0,0,-1)
glVertex3f(-1,-1,0)
glVertex3f(-1,1,0)
glVertex3f(1,1,0)
glVertex3f(1,-1,0)
glEnd()
glTranslatef(0,0,0)
glColor3f(0.5,0,0)
glStencilFunc(GL_ALWAYS, 2, -1)
glutSolidCube(1.0)
def mousePressEvent(self, event):
id = glReadPixels(event.x(), self.height - event.y() - 1, 1, 1, GL_STENCIL_INDEX, GL_UNSIGNED_INT)
print(id)
and the same is working properly when I am using this with GLUT window as following:
from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
from math import *
import numpy as np
window_width=0
window_height=0
def changeSize(w, h):
global window_height, window_width
window_width=w
window_height=h
# Prevent a divide by zero, when window is too short
# (you cant make a window of zero width).
if h==0:
h=1
ratio = w * 1.0 / h
# Use the Projection Matrix
glMatrixMode(GL_PROJECTION)
# Reset Matrix
glLoadIdentity()
# Set the viewport to be the entire window
glViewport(0, 0, w, h)
# Set the correct perspective.
gluPerspective(45.0, ratio, 0.1, 100.0)
# Get Back to the Modelview
glMatrixMode(GL_MODELVIEW)
def drawSome():
glStencilFunc(GL_ALWAYS, 1, -1)
glBegin(GL_QUADS)
glColor3f(0.0, 0.5, 0.0)
glNormal3f(0,0,-1)
glVertex3f(-1,-1,0)
glVertex3f(-1,1,0)
glVertex3f(1,1,0)
glVertex3f(1,-1,0)
glEnd()
glTranslatef(0,0,0)
glColor3f(0.5,0,0)
glStencilFunc(GL_ALWAYS, 2, -1)
glutSolidCube(1.0)
def renderScene():
glClearStencil(0)
glClear(GL_STENCIL_BUFFER_BIT)
glEnable(GL_STENCIL_TEST)
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE)
ambientColor = [0.2,0.2,0.2,1.0]
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambientColor)
lightColor0 = [0.5,0.5,0.5,1.0] #Color
lightPos0 = [0, 0, -10.0, 1.0]
glLightfv(GL_LIGHT0, GL_DIFFUSE, lightColor0)
glLightfv(GL_LIGHT0, GL_POSITION, lightPos0)
lightColor1 = [0.5, 0.2, 0.2, 1.0]
lightPos1 = [-1.0, 0.5, 0.5, 0.0]
glLightfv(GL_LIGHT1, GL_DIFFUSE, lightColor1)
glLightfv(GL_LIGHT1, GL_POSITION, lightPos1)
# Clear Color and Depth Buffers
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
# Reset transformations
glLoadIdentity()
# Set the camera
# gluLookAt(x, 1.0, z, x+lx, 1.0, z+lz, 0.0, 1.0, 0.0)
gluLookAt(-5, 5, -5, 0, 0, 0, 0.0, 1.0, 0.0)
drawSome()
glutSwapBuffers()
def mouse(button, state, x, y):
global window_height
id = glReadPixels(x, window_height - y - 1, 1, 1, GL_STENCIL_INDEX, GL_UNSIGNED_INT)
print(id[0][0])
if __name__ == '__main__':
glutInit()
glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA)
# glutInitWindowPosition(100,100)
glutInitWindowSize(320,320)
glutCreateWindow("Lighthouse3D - GLUT Tutorial")
glClearColor(1.0, 0.5, 0.2, 0.5)
# register callbacks
glutDisplayFunc(renderScene)
glutReshapeFunc(changeSize)
glutMouseFunc(mouse)
# glutTimerFunc(0, Timer, 0)
# here are the new entries
glutIgnoreKeyRepeat(1)
# OpenGL init
glEnable(GL_DEPTH_TEST)
glEnable(GL_LIGHTING)
glEnable(GL_COLOR_MATERIAL)
glEnable(GL_LIGHT0)
glEnable(GL_LIGHT1)
glEnable(GL_NORMALIZE)
glShadeModel(GL_SMOOTH)
glutMainLoop()
How to resolve the problem with QT5 GUI ?
I suspect there's no current OpenGL context when you call glReadPixels. You're mousePressEvent should probably call QOpenGLWidget::makeCurrent...
def mousePressEvent(self, event):
# Make the OpenGL context associated with this `QOpenGLWidget` current. and dont forget self
self.makeCurrent()
id = glReadPixels(event.x(), self.height - event.y() - 1, 1, 1, GL_STENCIL_INDEX, GL_UNSIGNED_INT)
print(id)
self.doneCurrent()
glReadPixels requires a render context to be current on the thread. Qt does automatic context managment and it's well documented that outside of methods named …GL the context may not be current.
You must make the context current explicitly outside of those methods, for which the Qt OpenGL classes offer the methods makeCurrent and doneCurrent. Brace your call of glReadPixels with calls to these functions; make sure to check, that makeCurrent succeeds, as the context may already be current in a different thread.