I was watching a video about pyglet and I tried to create a triangle:
import pyglet
from pyglet.gl import *
class mywindow(pyglet.window.Window):
def __init__(self, *args,**kwargs):
super().__init__(*args,**kwargs)
self.set_minimum_size(300,300)
window = mywindow(300,300,"deneme", True)
def on_draw():
glBegin(GL_TRIANGLE)
glColor3b(255,0,0)
glVertex2f(-1,0)
glColor3b(0,255,0)
glVertex2f(1,0)
glColor3b(0,0,255)
glVertex2f(0,1)
window.on_draw()
pyglet.app.run()
when I run this code; I get this error:
AttributeError: 'mywindow' object has no attribute 'on_draw'
Any idea how to solve this?
on_draw has to be a method of the class mywindow rather than a function. Don't invoke on_draw yourself, because it is called automatically, when the window is needed to be updated.
At the begin of on_draw you've to clear the display by (see Windowing).
A OpenGL immediate mode glBegin/glEnd sequence has to be ended by glEnd. The primitive type is GL_TRIANGLES rather than GL_TRIANGLE. If you want to specify the colors in range [0, 255], the you've to use glColor3ub (unsigned byte) rather then glColor3b (signed byte).
You have to set the viewport rectangle of the resizable window by glViewport in the on_resize event.
See the example:
import pyglet
from pyglet.gl import *
class mywindow(pyglet.window.Window):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_minimum_size(300,300)
def on_draw(self):
self.clear()
glBegin(GL_TRIANGLES)
glColor3ub(255, 0, 0)
glVertex2f(-1, 0)
glColor3ub(0, 255, 0)
glVertex2f(1, 0)
glColor3ub(0, 0, 255)
glVertex2f(0, 1)
glEnd()
def on_resize(self, width, height):
glViewport(0, 0, width, height)
window = mywindow(300,300,"deneme", True)
pyglet.app.run()
In [1]: from pyglet.gl import *
...:
...: window = pyglet.window.Window()
...:
...: vlist = pyglet.graphics.vertex_list(3, ('v2f', [0,0, 400,50, 200,300]))
...:
...: #window.event
...: def on_draw():
...: glClear(pyglet.gl.GL_COLOR_BUFFER_BIT)
...: glColor3f(1,0,0)
...: vlist.draw(GL_TRIANGLES)
...:
...: pyglet.app.run()
Output:
Related
The glGenVertexArrays function throws this excpetion in one script (loader.py): "OpenGL.error.NullFunctionError: Attempt to call an undefined function glGenVertexArrays", but not in the other script (display_manager.py)
I guess it has something to do with initialization.
loader.py (the 'create_vao' function is relevant)
from raw_model import RawModel
from OpenGL.GL import *
from OpenGL.GLUT import *
class Loader:
__vaos = []
__vbos = []
def load_to_vao(self, positions: list):
vao_id = self.create_vao()
self.store_data_in_attribute_list(0, positions)
self.unbind_vao()
return RawModel(vao_id, len(positions)//3)
#classmethod
def clean_up(cls):
for vao in cls.__vaos:
glDeleteVertexArrays(vao)
for vbo in cls.__vbos:
glDeleteBuffers(vbo)
#classmethod
def create_vao(cls):
vao_id = glGenVertexArrays(1)
cls.__vaos.append(vao_id)
glBindVertexArray(vao_id)
return vao_id
#classmethod
def store_data_in_attribute_list(cls, attribute_number: int, data: list):
vbo_id = glGenBuffers()
cls.__vbos.append(vbo_id)
glBindBuffer(GL_ARRAY_BUFFER, vbo_id)
glBufferData(GL_ARRAY_BUFFER, data, GL_STATIC_DRAW)
glVertexAttribPointer(attribute_number, 3, GL_FLOAT, False, 0, 0)
glBindBuffer(GL_ARRAY_BUFFER, 0)
#staticmethod
def unbind_vao():
glBindVertexArray(0)
display_manager.py (the 'create_display' function is relevant):
from OpenGL.GL import *
from OpenGL.GLUT import *
class DisplayManager:
def __init__(self, x: int = 1920, y: int = 1080):
if x is not None: self.__width = x
if y is not None: self.__height = y
def create_display(self, window_name):
glutInit()
glutInitDisplayMode(GLUT_RGBA) # initialize colors
glutInitWindowSize(self.get_width(), self.get_height()) # set windows size
glutInitWindowPosition(0, 0) # set window position
glutCreateWindow(f"{window_name}") # create window (with a name) and set window attribute
print(glGenVertexArrays(1))
input("This ran")
glutDisplayFunc(self.update_display)
glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS) # prevent program from stopping
#staticmethod
def update_display():
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) # Remove everything from screen (i.e. displays all white)
glLoadIdentity() # Reset all graphic/shape's position
glutSwapBuffers() # Important for double buffering
def get_width(self):
return self.__width
def get_height(self):
return self.__height
#classmethod
def set_window(cls, window):
cls.__WIND = window
I already tried to include the glutInit function into the loader.py script, with no success. I also tried to figure out in which scripts the glGenVertexArrays function works, with no real clue.
In my main.py function I called the create_vao method before I even created the window. Swapping the order did the trick.
I'm new to Pyqt5 and writing applications with it in Python so forgive me if this is a very simple question but I'm having trouble drawing ellipses in my program. I want to draw one by wherever a click occurs. Here is my code:
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import *
class Window(QGraphicsView):
def __init__(self):
super().__init__()
self.scene = QGraphicsScene()
self.setScene(self.scene)
# p.setTransform(transform)
self.button = QPushButton("Draw")
self.button.setCheckable(True)
self.button.setGeometry(0, 0, 100, 30)
self.scene.addWidget(self.button)
# self.setMouseTracking(True)
width, height = 1000, 1000
self.setFixedSize(width, height);
self.setSceneRect(0, 0, width, height);
self.fitInView(0, 0, width, height, Qt.KeepAspectRatio);
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.show()
def mousePressEvent(self, event):
if self.button.isChecked():
x = event.x()
y = event.y()
print(x, y)
ellipse = QGraphicsEllipseItem(x, y, 50, 20)
self.scene.addItem(ellipse)
The issue I'm having is I think the mousePressEvent function isn't allowing me to click on my button to enable drawing but the part I'm really not sure about is what is going on in the mousePressEvent. It seems as though it's getting the (x, y) coordinates within the QGraphicsView object but my ellipses are getting drawn in strange spots far away from wherever is clicked in my application when it's open.
You should not override the mousePressEvent as you remove the default behavior such as sending the event to the button. On the other hand you have to convert the coordinates of the view to the coordinates of the scene.
self.proxy_widget = self.scene.addWidget(self.button)
def mousePressEvent(self, event):
super().mousePressEvent(event)
vp = event.pos()
if self.proxy_widget in self.items(vp):
return
if self.button.isChecked():
ellipse = QGraphicsEllipseItem(0, 0, 50, 20)
self.scene.addItem(ellipse)
sp = self.mapToScene(vp)
ellipse.setPos(sp)
I am writing an application that shows the user's webcam video feed in a PyQT5 window. Using a QLabel and updating the label's pixmap for every frame is to slow due to the target device's performance.
I therefore tried to gain some speed by using OpenGL to display the video frames as 2D texture on a rectangle. I found this earlier question by user Arijit that contains a working example using GLUT. However, I fail to port the code from using GLUT to a QOpenGLWidget.
For the init and reshape functions in GLUT it is clear that they correspond to the initializeGL and resizeGL functions in QT. But where in the QOpenGLWidget lifecycle do the display and idle functions belong? Executing them inside paintGL shows no effect. The current code does not crash, but the widget stays black.
What is the right way to do this?
#!/usr/bin/env python
import sys, time
import cv2
import numpy as np
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class GLWidget(QOpenGLWidget):
def __init__(self, parent=None, width=1280, height=720):
self.parent = parent
self.width = width
self.height = height
QOpenGLWidget.__init__(self, parent)
def sizeHint(self):
return QSize(self.width,self.height)
def setImage(self,image):
self.image = np.flipud(image).tobytes()
self._idle()
def initializeGL(self):
version_profile = QOpenGLVersionProfile()
version_profile.setVersion(2,0)
self.gl = self.context().versionFunctions(version_profile)
self.gl.glClearColor(0.0, 0.0, 0.0, 1.0)
self.setImage(np.zeros((self.width, self.height,3)))
def _idle(self):
self.gl.glTexImage2D(self.gl.GL_TEXTURE_2D,
0,
self.gl.GL_RGB,
self.width,self.height,
0,
self.gl.GL_RGB,
self.gl.GL_UNSIGNED_BYTE,
self.image)
self.update()
def _display(self):
self.gl.glClear(self.gl.GL_COLOR_BUFFER_BIT | self.gl.GL_DEPTH_BUFFER_BIT)
self.gl.glEnable(self.gl.GL_TEXTURE_2D)
self.gl.glTexParameterf(self.gl.GL_TEXTURE_2D, self.gl.GL_TEXTURE_MIN_FILTER, self.gl.GL_NEAREST)
self.gl.glMatrixMode(self.gl.GL_PROJECTION)
self.gl.glLoadIdentity()
self.gl.glOrtho(0, self.width, 0, self.height,-1,1)
self.gl.glMatrixMode(self.gl.GL_MODELVIEW)
self.gl.glLoadIdentity()
self.gl.glBegin(self.gl.GL_QUADS)
self.gl.glTexCoord2f(0.0, 0.0)
self.gl.glVertex2f(0.0, 0.0)
self.gl.glTexCoord2f(1.0, 0.0)
self.gl.glVertex2f(self.width, 0.0)
self.gl.glTexCoord2f(1.0, 1.0)
self.gl.glVertex2f(self.width, self.height)
self.gl.glTexCoord2f(0.0, 1.0)
self.gl.glVertex2f(0.0, self.height)
self.gl.glEnd()
self.gl.glFlush()
def resizeGL(self, w, h):
if h == 0:
h = 1
self.gl.glViewport(0, 0, w, h)
self.gl.glMatrixMode(self.gl.GL_PROJECTION)
self.gl.glLoadIdentity()
if w <= h:
self.gl.glOrtho(-1, 1, -1*h/w, h/w, -1, 1)
else:
self.gl.glOrtho(-1*w/h, w/h, -1, 1, -1, 1)
self.gl.glMatrixMode(self.gl.GL_MODELVIEW)
self.gl.glLoadIdentity()
def paintGL(self):
self._display()
class VideoThread(QThread):
change_image_signal = pyqtSignal(np.ndarray)
def __init__(self):
super().__init__()
self._run_flag = True
def run(self):
capture = cv2.VideoCapture(0)
capture.set(cv2.CAP_PROP_BUFFERSIZE,3)
while self._run_flag:
_, frame = capture.read()
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
self.change_image_signal.emit(frame)
time.sleep(0.4)
capture.release()
def stop(self):
self._run_flag = False
self.wait()
class MainUI(QWidget):
def __init__(self):
QWidget.__init__(self)
self.video_size = QSize(394,292)
self.setup_ui()
self.setup_camera()
def setup_ui(self):
self.video_widget = GLWidget(self,self.video_size.width(),self.video_size.height())
self.main_layout = QGridLayout()
self.main_layout.addWidget(self.video_widget,0,0)
self.setLayout(self.main_layout)
def closeEvent(self, event):
self.thread.stop()
event.accept()
def setup_camera(self):
self.thread = VideoThread()
self.thread.change_image_signal.connect(self.display_video_stream)
self.thread.start()
#pyqtSlot(np.ndarray)
def display_video_stream(self,image):
self.video_widget.setImage(image)
if __name__ == "__main__":
app = QApplication(sys.argv)
win = MainUI()
win.show()
sys.exit(app.exec())
imports
from pyglet.gl import *
import pyglet
from pyglet.window import key
the code
self.batch = pyglet.graphics.Batch
x,y,z = 0,0,0
X,Y,Z = x+1,y+1,z+1
color = ('c3f', (1,1,1)*4)
self.batch.add(4, pyglet.gl.GL_QUADS, None, ('v3f', (x,y,z, X,y,z, X,Y,z, x,Y,z)), color) # problem here
My ide says that function got "4" aka int and expected batch, but i looked at some documentation and it was the same as in the code above.
i am using python 3.8.
Oh and if needed here is all of my code (i do not think you need it):
from pyglet.gl import *
import pyglet
from pyglet.window import key
import math
WIDTH = 1000
HEIGHT = math.floor(WIDTH / 12 * 9)
class Model:
def __init__(self):
self.batch = pyglet.graphics.Batch
x,y,z = 0,0,0
X,Y,Z = x+1,y+1,z+1
color = ('c3f', (1,1,1)*4)
self.batch.add(4, pyglet.gl.GL_QUADS, None, ('v3f', (x,y,z, X,y,z, X,Y,z, x,Y,z)), color)
def draw(self):
self.batch.draw()
class Window(pyglet.window.Window):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_minimum_size(math.floor(WIDTH*0.5), math.floor(HEIGHT*0.5))
self.model = Model()
def on_draw(self):
self.clear()
self.model.draw()
if __name__ == '__main__':
window = Window(width=WIDTH, height=HEIGHT, caption="pyglet.gl test", resizable=True)
glClearColor(0, 160, 160, 1)
pyglet.app.run()
Oh, I sloved it my self. Im just stupid.
I said:
self.batch = pyglet.graphics.Batch
But i had to add () at the end:
self.batch = pyglet.graphics.Batch()
I am running through a cumulation of OpenGL shader tutorials and mixing and matching stuff, trying to get a custom shader implemented. I have the following Python code and traceback:
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QOpenGLWidget
from PyQt5.QtCore import Qt
from OpenGL.GL import (
glLoadIdentity, glTranslatef, glRotatef,
glClear, glBegin, glEnd,
glColor3fv, glVertex3fv,
GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT,
GL_QUADS, GL_LINES,
shaders, GL_VERTEX_SHADER, GL_FRAGMENT_SHADER
)
from OpenGL.GLU import gluPerspective
class mainWindow(QMainWindow): #Main class.
def keyPressEvent(self, event): #This is the keypress detector.
try:
key = event.key()
except:
key = -1
#print(key)
if key == 16777216:
exit()
vertices = [
(-1, 1, 0),
(1, 1, 0),
(1, -1, 0),
(-1, -1, 0)
]
wires = [
(0, 1),
(1, 2),
(2, 3),
(0, 3)
]
facets = [
(0, 1, 2, 3)
]
zoomLevel = -5
rotateDegreeH = 0
rotateDegreeV = -45
vertShaderCode = """#version 120
void main() {
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}"""
fragShaderCode = """#version 120
void main() {
gl_FragColor = vec4( 0, 1, 0, 1 );
}"""
def __init__(self):
super(mainWindow, self).__init__()
self.sizeX = 700 #Variables used for the setting of the size of everything
self.sizeY = 600
self.setGeometry(0, 0, self.sizeX + 50, self.sizeY) #Set the window size
#make shaders
VERTEX_SHADER = shaders.compileShader(self.vertShaderCode, GL_VERTEX_SHADER)
FRAGMENT_SHADER = shaders.compileShader(self.fragShaderCode, GL_FRAGMENT_SHADER)
self.shader = shaders.compileProgram(VERTEX_SHADER,FRAGMENT_SHADER)
self.openGLWidget = QOpenGLWidget(self) #Create the GLWidget
self.openGLWidget.setGeometry(0, 0, self.sizeX, self.sizeY)
self.openGLWidget.initializeGL()
self.openGLWidget.resizeGL(self.sizeX, self.sizeY) #Resize GL's knowledge of the window to match the physical size?
self.openGLWidget.paintGL = self.paintGL #override the default function with my own?
def nav(self, hVal = 0, vVal = 0, zVal = 0):
self.zoomLevel += zVal
self.rotateDegreeH += hVal
self.rotateDegreeV += vVal
self.openGLWidget.update()
def paintGL(self):
#This function uses shape objects, such as cube() or mesh(). Shape objects require the following:
#a list named 'vertices' - This list is a list of points, from which edges and faces are drawn.
#a list named 'wires' - This list is a list of tuples which refer to vertices, dictating where to draw wires.
#a list named 'facets' - This list is a list of tuples which refer to vertices, ditating where to draw facets.
#a bool named 'render' - This bool is used to dictate whether or not to draw the shape.
#a bool named 'drawWires' - This bool is used to dictate whether wires should be drawn.
#a bool named 'drawFaces' - This bool is used to dictate whether facets should be drawn.
shaders.glUseProgram(self.shader)
glLoadIdentity()
gluPerspective(45, self.sizeX / self.sizeY, 0.1, 110.0) #set perspective?
glTranslatef(0, 0, self.zoomLevel) #I used -10 instead of -2 in the PyGame version.
glRotatef(self.rotateDegreeV, 1, 0, 0) #I used 2 instead of 1 in the PyGame version.
glRotatef(self.rotateDegreeH, 0, 0, 1)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
glBegin(GL_LINES)
for w in self.wires:
for v in w:
glVertex3fv(self.vertices[v])
glEnd()
glBegin(GL_QUADS)
for f in self.facets:
for v in f:
glVertex3fv(self.vertices[v])
glEnd()
app = QApplication([])
window = mainWindow()
window.show()
sys.exit(app.exec_())
C:\Users\ccronk22\Documents\Python\glShaders>pygl002.py
Traceback (most recent call last):
File "C:\Users\ccronk22\AppData\Local\Programs\Python\Python38\lib\site-packages\OpenGL\latebind.py", line 43, in __call__
return self._finalCall( *args, **named )
TypeError: 'NoneType' object is not callable
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "C:\Users\ccronk22\Documents\Python\glShaders\pyGL002.py", line 109, in <module>
window = mainWindow()
File "C:\Users\ccronk22\Documents\Python\glShaders\pyGL002.py", line 59, in __init__
VERTEX_SHADER = shaders.compileShader(self.vertShaderCode, GL_VERTEX_SHADER)
File "C:\Users\ccronk22\AppData\Local\Programs\Python\Python38\lib\site-packages\OpenGL\GL\shaders.py", line 228, in compileShader
shader = glCreateShader(shaderType)
File "C:\Users\ccronk22\AppData\Local\Programs\Python\Python38\lib\site-packages\OpenGL\latebind.py", line 46, in __call__
self._finalCall = self.finalise()
File "C:\Users\ccronk22\AppData\Local\Programs\Python\Python38\lib\site-packages\OpenGL\extensions.py", line 242, in finalise
raise error.NullFunctionError(
OpenGL.error.NullFunctionError: Attempt to call an undefined alternate function (glCreateShader, glCreateShaderObjectARB), check for bool(glCreateShader) before calling
C:\Users\ccronk22\Documents\Python\glShaders>
The above code works well if you comment out the compileShader, compileProgram, and useProgram lines. It produces a white square, viewed from an above angle:
def __init__(self):
super(mainWindow, self).__init__()
self.sizeX = 700 #Variables used for the setting of the size of everything
self.sizeY = 600
self.setGeometry(0, 0, self.sizeX + 50, self.sizeY) #Set the window size
#make shaders
#VERTEX_SHADER = shaders.compileShader(self.vertShaderCode, GL_VERTEX_SHADER)
#FRAGMENT_SHADER = shaders.compileShader(self.fragShaderCode, GL_FRAGMENT_SHADER)
#self.shader = shaders.compileProgram(VERTEX_SHADER,FRAGMENT_SHADER)
[…]
def paintGL(self):
#This function uses shape objects, such as cube() or mesh(). Shape objects require the following:
#a list named 'vertices' - This list is a list of points, from which edges and faces are drawn.
#a list named 'wires' - This list is a list of tuples which refer to vertices, dictating where to draw wires.
#a list named 'facets' - This list is a list of tuples which refer to vertices, ditating where to draw facets.
#a bool named 'render' - This bool is used to dictate whether or not to draw the shape.
#a bool named 'drawWires' - This bool is used to dictate whether wires should be drawn.
#a bool named 'drawFaces' - This bool is used to dictate whether facets should be drawn.
#shaders.glUseProgram(self.shader)
Some additional information:
I am attempting to make the code from http://pyopengl.sourceforge.net/context/tutorials/shader_1.html work in the PyQt5 context, with only the OpenGL and PyQt5 APIs. The tutorial was written in Python2. I am using Python3.
So, what am I doing wrong with my shader compilation? Is it in the Python or GLSL?
To compile and link the shader you need a valid and current OpenGL Context. Note the OpenGL context have to be current when any OpenGL instruction is invoked.
QOpenGLWidget provides the virtual methods
def initializeGL ()
def paintGL ()
def resizeGL (w, h)
where the OpenGL context is active. This methods are callbacks and invoked by Qts event handling. Don't call them in your code.
Create the shader in the initializeGL callback:
class mainWindow(QMainWindow):
def __init__(self):
super(mainWindow, self).__init__()
self.sizeX = 700 #Variables used for the setting of the size of everything
self.sizeY = 600
self.setGeometry(0, 0, self.sizeX + 50, self.sizeY) #Set the window size
self.openGLWidget = QOpenGLWidget(self) #Create the GLWidget
self.openGLWidget.setGeometry(0, 0, self.sizeX, self.sizeY)
self.openGLWidget.resizeGL(self.sizeX, self.sizeY) #Resize GL's knowledge of the window to match the physical size?
self.openGLWidget.initializeGL = self.initializeGL
self.openGLWidget.paintGL = self.paintGL #override the default function with my own?
self.shader = None
def nav(self, hVal = 0, vVal = 0, zVal = 0):
self.zoomLevel += zVal
self.rotateDegreeH += hVal
self.rotateDegreeV += vVal
self.openGLWidget.update()
def initializeGL(self):
#make shaders
VERTEX_SHADER = shaders.compileShader(self.vertShaderCode, GL_VERTEX_SHADER)
FRAGMENT_SHADER = shaders.compileShader(self.fragShaderCode, GL_FRAGMENT_SHADER)
self.shader = shaders.compileProgram(VERTEX_SHADER,FRAGMENT_SHADER)
def paintGL(self):
glUseProgram(self.shader)
# [...]
(The import of glUseProgram is missing in your code)