Related
From this answer I have code that adds a number of Widgets to a PyQt5 GUI:
I would like to add another widget just in this configuration:
I was trying to play around with QGridLayout(), for a simplified version of the problem (MWE below).
What I tried (MWE below). I get something like this (plots all squashed on one side):
and varying the grid coordinates has no effect whatsoever.
MWE:
import PyQt5
from PyQt5 import QtGui, QtCore
import pyqtgraph as pg
import sys
import numpy as np
width = 1000
height = 500
class layout():
def setup(self, window):
self.window = window
self.window.resize(width, height)
grid = PyQt5.QtWidgets.QGridLayout()
self.dialogue = QtGui.QTextEdit()
grid.addWidget(self.dialogue , 100, 0)
self.plot = pg.GraphicsLayoutWidget(self.window)
grid.addWidget(self.plot , 200, 200)
class Window(pg.Qt.QtGui.QMainWindow, layout):
def __init__(self, shot = None):
super(Window, self).__init__()
self.setup(self)
self.show()
if __name__ == '__main__':
app = pg.Qt.QtGui.QApplication([])
Window()
sys.exit(app.exec_())
The errors in the code provided by the OP are:
The layout (QGridLayout) was never set in a widget. The Qt layouts are not visual elements but managers of the geometry of the widgets.
A centralWidget must be set if a QMainWindow is used.
import sys
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui, QtCore
width = 1000
height = 500
class layout:
def setup(self, window):
self.window = window
self.window.resize(width, height)
grid = QtGui.QGridLayout()
self.dialogue = QtGui.QTextEdit()
grid.addWidget(self.dialogue, 100, 0)
self.plot = pg.GraphicsLayoutWidget()
grid.addWidget(self.plot, 200, 200)
central_widget = QtGui.QWidget()
window.setCentralWidget(central_widget)
central_widget.setLayout(grid)
class Window(pg.Qt.QtGui.QMainWindow, layout):
def __init__(self, shot=None):
super(Window, self).__init__()
self.setup(self)
if __name__ == "__main__":
app = QtGui.QApplication([])
w = Window()
w.show()
sys.exit(app.exec_())
Anyway, that problem has nothing to do with the initial problem.
Probably (since the OP does not provide any attempt to the initial goal) the error is that it is thought that adding the same widget 2 times will create 2 copies but it is not, when you add a widget to a layout then it will be removed from its previous position. The solution is to create 2 widgets. For this, it is better to create a class that allows to implement this logic in a simple way.
import sys
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
from glumpy import app as glumpy_app, gl, gloo, data, library
from glumpy.geometry import primitives
from glumpy.transforms import Trackball
width = 1000
height = 500
glumpy_app.use("qt5")
vertex = """
#include "misc/spatial-filters.frag"
uniform float height;
uniform sampler2D data;
uniform vec2 data_shape;
attribute vec3 position;
attribute vec2 texcoord;
varying vec3 v_position;
varying vec2 v_texcoord;
void main()
{
float z = height*Bicubic(data, data_shape, texcoord).r;
gl_Position = <transform>;
v_texcoord = texcoord;
v_position = vec3(position.xy, z);
}
"""
fragment = """
#include "misc/spatial-filters.frag"
uniform mat4 model;
uniform mat4 view;
uniform mat4 normal;
uniform sampler2D texture;
uniform float height;
uniform vec4 color;
uniform sampler2D data;
uniform vec2 data_shape;
uniform vec3 light_color[3];
uniform vec3 light_position[3];
varying vec3 v_position;
varying vec2 v_texcoord;
float lighting(vec3 v_normal, vec3 light_position)
{
// Calculate normal in world coordinates
vec3 n = normalize(normal * vec4(v_normal,1.0)).xyz;
// Calculate the location of this fragment (pixel) in world coordinates
vec3 position = vec3(view * model * vec4(v_position, 1));
// Calculate the vector from this pixels surface to the light source
vec3 surface_to_light = light_position - position;
// Calculate the cosine of the angle of incidence (brightness)
float brightness = dot(n, surface_to_light) /
(length(surface_to_light) * length(n));
brightness = max(min(brightness,1.0),0.0);
return brightness;
}
void main()
{
mat4 model = <transform.trackball_model>;
// Extract data value
float value = Bicubic(data, data_shape, v_texcoord).r;
// Compute surface normal using neighbour values
float hx0 = height*Bicubic(data, data_shape, v_texcoord+vec2(+1,0)/data_shape).r;
float hx1 = height*Bicubic(data, data_shape, v_texcoord+vec2(-1,0)/data_shape).r;
float hy0 = height*Bicubic(data, data_shape, v_texcoord+vec2(0,+1)/data_shape).r;
float hy1 = height*Bicubic(data, data_shape, v_texcoord+vec2(0,-1)/data_shape).r;
vec3 dx = vec3(2.0/data_shape.x,0.0,hx0-hx1);
vec3 dy = vec3(0.0,2.0/data_shape.y,hy0-hy1);
vec3 v_normal = normalize(cross(dx,dy));
// Map value to rgb color
float c = 0.6 + 0.4*texture2D(texture, v_texcoord).r;
vec4 l1 = vec4(light_color[0] * lighting(v_normal, light_position[0]), 1);
vec4 l2 = vec4(light_color[1] * lighting(v_normal, light_position[1]), 1);
vec4 l3 = vec4(light_color[2] * lighting(v_normal, light_position[2]), 1);
gl_FragColor = color * vec4(c,c,c,1) * (0.5 + 0.5*(l1+l2+l3));
} """
def func3(x, y):
return (1 - x / 2 + x ** 5 + y ** 3) * np.exp(-(x ** 2) - y ** 2)
class Viewer(QtGui.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.glumpy_window = glumpy_app.Window(color=(1, 1, 1, 1))
lay = QtGui.QVBoxLayout(self)
lay.addWidget(self.glumpy_window._native_window)
n = 64
self.surface = gloo.Program(vertex, fragment)
self.vertices, self.s_indices = primitives.plane(2.0, n=n)
self.surface.bind(self.vertices)
I = []
for i in range(n):
I.append(i)
for i in range(1, n):
I.append(n - 1 + i * n)
for i in range(n - 1):
I.append(n * n - 1 - i)
for i in range(n - 1):
I.append(n * (n - 1) - i * n)
self.b_indices = np.array(I, dtype=np.uint32).view(gloo.IndexBuffer)
x = np.linspace(-2.0, 2.0, 32).astype(np.float32)
y = np.linspace(-2.0, 2.0, 32).astype(np.float32)
X, Y = np.meshgrid(x, y)
Z = func3(X, Y)
self.surface["data"] = (Z - Z.min()) / (Z.max() - Z.min())
self.surface["data"].interpolation = gl.GL_NEAREST
self.surface["data_shape"] = Z.shape[1], Z.shape[0]
self.surface["u_kernel"] = data.get("spatial-filters.npy")
self.surface["u_kernel"].interpolation = gl.GL_LINEAR
self.surface["texture"] = data.checkerboard(32, 24)
self.transform = Trackball("vec4(position.xy, z, 1.0)")
self.surface["transform"] = self.transform
self.glumpy_window.attach(self.transform)
T = (Z - Z.min()) / (Z.max() - Z.min())
self.surface["height"] = 0.75
self.surface["light_position[0]"] = 3, 0, 0 + 5
self.surface["light_position[1]"] = 0, 3, 0 + 5
self.surface["light_position[2]"] = -3, -3, +5
self.surface["light_color[0]"] = 1, 0, 0
self.surface["light_color[1]"] = 0, 1, 0
self.surface["light_color[2]"] = 0, 0, 1
phi, theta = -45, 0
self.time = 0
self.glumpy_window.set_handler("on_init", self.on_init)
self.glumpy_window.set_handler("on_draw", self.on_draw)
def on_init(self):
gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
gl.glPolygonOffset(1, 1)
gl.glEnable(gl.GL_LINE_SMOOTH)
gl.glLineWidth(2.5)
def on_draw(self, dt):
self.time += dt
self.glumpy_window.clear()
self.surface["data"]
gl.glDisable(gl.GL_BLEND)
gl.glEnable(gl.GL_DEPTH_TEST)
gl.glEnable(gl.GL_POLYGON_OFFSET_FILL)
self.surface["color"] = 1, 1, 1, 1
self.surface.draw(gl.GL_TRIANGLES, self.s_indices)
gl.glDisable(gl.GL_POLYGON_OFFSET_FILL)
gl.glEnable(gl.GL_BLEND)
gl.glDepthMask(gl.GL_FALSE)
self.surface["color"] = 0, 0, 0, 1
self.surface.draw(gl.GL_LINE_LOOP, self.b_indices)
gl.glDepthMask(gl.GL_TRUE)
model = self.surface["transform"]["model"].reshape(4, 4)
view = self.surface["transform"]["view"].reshape(4, 4)
self.surface["view"] = view
self.surface["model"] = model
self.surface["normal"] = np.array(np.matrix(np.dot(view, model)).I.T)
self.surface["height"] = 0.75 * np.cos(self.time)
def showEvent(self, event):
super().showEvent(event)
self.glumpy_window.dispatch_event("on_resize", *self.glumpy_window.get_size())
class layout:
def setup(self, window):
self.window = window
self.window.resize(width, height)
self.dialogue = QtGui.QTextEdit()
self.plot = pg.GraphicsLayoutWidget(self.window)
self.plot1 = self.plot.addPlot(colspan=1)
self.centralwidget = QtGui.QWidget(self.window)
self.horizontallayout = QtGui.QHBoxLayout(self.centralwidget)
self.window.setCentralWidget(self.centralwidget)
self.top_viewer = Viewer()
self.bottom_viewer = Viewer()
right_container = QtGui.QWidget()
lay = QtGui.QVBoxLayout(right_container)
lay.addWidget(self.top_viewer)
lay.addWidget(self.bottom_viewer)
self.horizontallayout.addWidget(self.dialogue, stretch=1)
self.horizontallayout.addWidget(self.plot, stretch=1)
self.horizontallayout.addWidget(right_container, stretch=1)
class Window(QtGui.QMainWindow, layout):
def __init__(self, shot=None):
super(Window, self).__init__()
self.setup(self)
def closeEvent(self, event):
super().closeEvent(event)
for viewer in (self.top_viewer, self.bottom_viewer):
viewer.glumpy_window.close()
if __name__ == "__main__":
app = pg.Qt.QtGui.QApplication([])
w = Window()
w.show()
glumpy_app.run()
I have a simple GUI onto which I am plotting images. I create Widgets for text outputs and arrays plots, and I would like to add one for 3D visualtion using glumpy, say to show this example from the glumpy documentation.
What I would like is an "extra" slot on which to have the glumpy output:
I saw for example in this GitHub thread that people were referring to PyQt5 and glumpy integration, but I only see snippets of code and noting that works as a standalone example.
Also, it seems glumpy is already using PyQt5 in the backend (here), but I don't understand this well enough to know if and how I can access it a posteriori?
This is my MWE:
from PyQt5 import QtGui, QtCore
import pyqtgraph as pg
import sys
width = 1000
height = 500
class layout():
def setup(self, window):
self.window = window
self.window.resize(width, height)
self.centralwidget = QtGui.QWidget(self.window)
self.horizontallayout = QtGui.QHBoxLayout(self.centralwidget)
self.window.setCentralWidget(self.centralwidget)
self.dialogue = QtGui.QTextEdit()
self.horizontallayout.addWidget(self.dialogue)
self.plot = pg.GraphicsLayoutWidget(self.window)
self.horizontallayout.addWidget(self.plot)
self.plot1 = self.plot.addPlot(colspan=1)
class Window(pg.Qt.QtGui.QMainWindow, layout):
def __init__(self, shot = None):
super(Window, self).__init__()
self.setup(self)
self.show()
if __name__ == '__main__':
app = pg.Qt.QtGui.QApplication([])
Window()
sys.exit(app.exec_())
You have to get the internal QGLWidget through the "_native_window" attribute. The following example is based on the official example geometry-surface.py .
import sys
from PyQt5 import QtGui, QtCore
import pyqtgraph as pg
import numpy as np
from glumpy import app as glumpy_app, gl, gloo, data, library
from glumpy.geometry import primitives
from glumpy.transforms import Trackball
width = 1000
height = 500
glumpy_app.use("qt5")
vertex = """
#include "misc/spatial-filters.frag"
uniform float height;
uniform sampler2D data;
uniform vec2 data_shape;
attribute vec3 position;
attribute vec2 texcoord;
varying vec3 v_position;
varying vec2 v_texcoord;
void main()
{
float z = height*Bicubic(data, data_shape, texcoord).r;
gl_Position = <transform>;
v_texcoord = texcoord;
v_position = vec3(position.xy, z);
}
"""
fragment = """
#include "misc/spatial-filters.frag"
uniform mat4 model;
uniform mat4 view;
uniform mat4 normal;
uniform sampler2D texture;
uniform float height;
uniform vec4 color;
uniform sampler2D data;
uniform vec2 data_shape;
uniform vec3 light_color[3];
uniform vec3 light_position[3];
varying vec3 v_position;
varying vec2 v_texcoord;
float lighting(vec3 v_normal, vec3 light_position)
{
// Calculate normal in world coordinates
vec3 n = normalize(normal * vec4(v_normal,1.0)).xyz;
// Calculate the location of this fragment (pixel) in world coordinates
vec3 position = vec3(view * model * vec4(v_position, 1));
// Calculate the vector from this pixels surface to the light source
vec3 surface_to_light = light_position - position;
// Calculate the cosine of the angle of incidence (brightness)
float brightness = dot(n, surface_to_light) /
(length(surface_to_light) * length(n));
brightness = max(min(brightness,1.0),0.0);
return brightness;
}
void main()
{
mat4 model = <transform.trackball_model>;
// Extract data value
float value = Bicubic(data, data_shape, v_texcoord).r;
// Compute surface normal using neighbour values
float hx0 = height*Bicubic(data, data_shape, v_texcoord+vec2(+1,0)/data_shape).r;
float hx1 = height*Bicubic(data, data_shape, v_texcoord+vec2(-1,0)/data_shape).r;
float hy0 = height*Bicubic(data, data_shape, v_texcoord+vec2(0,+1)/data_shape).r;
float hy1 = height*Bicubic(data, data_shape, v_texcoord+vec2(0,-1)/data_shape).r;
vec3 dx = vec3(2.0/data_shape.x,0.0,hx0-hx1);
vec3 dy = vec3(0.0,2.0/data_shape.y,hy0-hy1);
vec3 v_normal = normalize(cross(dx,dy));
// Map value to rgb color
float c = 0.6 + 0.4*texture2D(texture, v_texcoord).r;
vec4 l1 = vec4(light_color[0] * lighting(v_normal, light_position[0]), 1);
vec4 l2 = vec4(light_color[1] * lighting(v_normal, light_position[1]), 1);
vec4 l3 = vec4(light_color[2] * lighting(v_normal, light_position[2]), 1);
gl_FragColor = color * vec4(c,c,c,1) * (0.5 + 0.5*(l1+l2+l3));
} """
def func3(x, y):
return (1 - x / 2 + x ** 5 + y ** 3) * np.exp(-(x ** 2) - y ** 2)
class layout:
def setup(self, window):
self.window = window
self.window.resize(width, height)
self.dialogue = QtGui.QTextEdit()
self.plot = pg.GraphicsLayoutWidget(self.window)
self.plot1 = self.plot.addPlot(colspan=1)
self.glumpy_window = glumpy_app.Window(color=(1, 1, 1, 1))
self.glumpy_window._native_window
self.centralwidget = QtGui.QWidget(self.window)
self.horizontallayout = QtGui.QHBoxLayout(self.centralwidget)
self.window.setCentralWidget(self.centralwidget)
self.horizontallayout.addWidget(self.dialogue, stretch=1)
self.horizontallayout.addWidget(self.plot, stretch=1)
self.horizontallayout.addWidget(self.glumpy_window._native_window, stretch=1)
class Window(pg.Qt.QtGui.QMainWindow, layout):
def __init__(self, shot=None):
super(Window, self).__init__()
self.setup(self)
n = 64
self.surface = gloo.Program(vertex, fragment)
self.vertices, self.s_indices = primitives.plane(2.0, n=n)
self.surface.bind(self.vertices)
I = []
for i in range(n):
I.append(i)
for i in range(1, n):
I.append(n - 1 + i * n)
for i in range(n - 1):
I.append(n * n - 1 - i)
for i in range(n - 1):
I.append(n * (n - 1) - i * n)
self.b_indices = np.array(I, dtype=np.uint32).view(gloo.IndexBuffer)
x = np.linspace(-2.0, 2.0, 32).astype(np.float32)
y = np.linspace(-2.0, 2.0, 32).astype(np.float32)
X, Y = np.meshgrid(x, y)
Z = func3(X, Y)
self.surface["data"] = (Z - Z.min()) / (Z.max() - Z.min())
self.surface["data"].interpolation = gl.GL_NEAREST
self.surface["data_shape"] = Z.shape[1], Z.shape[0]
self.surface["u_kernel"] = data.get("spatial-filters.npy")
self.surface["u_kernel"].interpolation = gl.GL_LINEAR
self.surface["texture"] = data.checkerboard(32, 24)
self.transform = Trackball("vec4(position.xy, z, 1.0)")
self.surface["transform"] = self.transform
self.glumpy_window.attach(self.transform)
T = (Z - Z.min()) / (Z.max() - Z.min())
self.surface["height"] = 0.75
self.surface["light_position[0]"] = 3, 0, 0 + 5
self.surface["light_position[1]"] = 0, 3, 0 + 5
self.surface["light_position[2]"] = -3, -3, +5
self.surface["light_color[0]"] = 1, 0, 0
self.surface["light_color[1]"] = 0, 1, 0
self.surface["light_color[2]"] = 0, 0, 1
phi, theta = -45, 0
self.time = 0
self.glumpy_window.set_handler("on_init", self.on_init)
self.glumpy_window.set_handler("on_draw", self.on_draw)
def on_init(self):
gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
gl.glPolygonOffset(1, 1)
gl.glEnable(gl.GL_LINE_SMOOTH)
gl.glLineWidth(2.5)
def on_draw(self, dt):
self.time += dt
self.glumpy_window.clear()
self.surface["data"]
gl.glDisable(gl.GL_BLEND)
gl.glEnable(gl.GL_DEPTH_TEST)
gl.glEnable(gl.GL_POLYGON_OFFSET_FILL)
self.surface["color"] = 1, 1, 1, 1
self.surface.draw(gl.GL_TRIANGLES, self.s_indices)
gl.glDisable(gl.GL_POLYGON_OFFSET_FILL)
gl.glEnable(gl.GL_BLEND)
gl.glDepthMask(gl.GL_FALSE)
self.surface["color"] = 0, 0, 0, 1
self.surface.draw(gl.GL_LINE_LOOP, self.b_indices)
gl.glDepthMask(gl.GL_TRUE)
model = self.surface["transform"]["model"].reshape(4, 4)
view = self.surface["transform"]["view"].reshape(4, 4)
self.surface["view"] = view
self.surface["model"] = model
self.surface["normal"] = np.array(np.matrix(np.dot(view, model)).I.T)
self.surface["height"] = 0.75 * np.cos(self.time)
def showEvent(self, event):
super().showEvent(event)
self.glumpy_window.dispatch_event("on_resize", *self.glumpy_window.get_size())
def closeEvent(self, event):
super().closeEvent(event)
self.glumpy_window.close()
if __name__ == "__main__":
app = pg.Qt.QtGui.QApplication([])
w = Window()
w.show()
glumpy_app.run()
I have written a code to render a triangle using a shader program. I want to rotate the triangle. I'm using PyGLM to set a transformation matrix. Here I'm presenting the whole code. If I run this code a triangle is appearing in the window as expected, but there is no rotation. I think I've failed to pass the transformation matrix to the buffer.
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
from OpenGL.GL import shaders
import numpy as np
import glm
VERTEX_SHADER = """
#version 330
in vec4 position;
in vec3 color;
out vec3 newColor;
void main()
{
gl_Position = position;
newColor = color;
}
"""
FRAGMENT_SHADER = """
#version 330
in vec3 newColor;
out vec4 outColor;
void main()
{
outColor = vec4(newColor,1.0f);
}
"""
shaderProgram = None
def initliaze():
global VERTEXT_SHADER
global FRAGMEN_SHADER
global shaderProgram
vertexshader = shaders.compileShader(VERTEX_SHADER, GL_VERTEX_SHADER)
fragmentshader = shaders.compileShader(FRAGMENT_SHADER, GL_FRAGMENT_SHADER)
shaderProgram = shaders.compileProgram(vertexshader, fragmentshader)
triangles = [-0.5, -0.5, 0.0, 1.0,0.0,0.0,
0.5, -0.5, 0.0, 0.0,1.0,0.0,
0.0, 0.5, 0.0, 0,0,0.0,1.0]
triangles = np.array(triangles, dtype=np.float32)
VBO = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, VBO)
glBufferData(GL_ARRAY_BUFFER, triangles.nbytes, triangles, GL_DYNAMIC_DRAW)
position = glGetAttribLocation(shaderProgram, 'position')
glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, 24, ctypes.c_void_p(0))
glEnableVertexAttribArray(position)
color = glGetAttribLocation(shaderProgram, 'color')
glVertexAttribPointer(color, 3, GL_FLOAT, GL_FALSE, 24, ctypes.c_void_p(12))
glEnableVertexAttribArray(color)
def render():
global shaderProgram
global angle
#shader
glUseProgram(shaderProgram)
glClearColor(0, 0, 0, 1)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
#transform matrix
transform = glm.mat4(1)
transform = glm.translate(transform, glm.vec3(0.5,-0.5,0.0))
transform = glm.rotate(transform, glutGet(GLUT_ELAPSED_TIME),glm.vec3(0,0,1))
transformLoc = glGetUniformLocation(shaderProgram,"transform")
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm.value_ptr(transform))
#render program
glDrawArrays(GL_TRIANGLES, 0, 3)
glUseProgram(0)
glutSwapBuffers()
def main():
glutInit([])
glutInitWindowSize(640, 480)
glutCreateWindow("pyopengl with glut 2")
initliaze()
glutDisplayFunc(render)
glutMainLoop()
if __name__ == '__main__':
main()
In VERTEX_SHADER you didn't mentioned transform variable. So your triangle position remain fixed after you run the program. Change your VERTEX_SHADER as following.
VERTEX_SHADER = """
#version 330
in vec4 position;
in vec3 color;
out vec3 newColor;
uniform mat4 transform;
void main()
{
gl_Position = transform*position;
newColor = color;
}
"""
In your code you are accessing the location of location of a uniform variable transform by following line.
transformLoc = glGetUniformLocation(shaderProgram,"transform")
You should add glutPostRedisplay() function after the glutSwapBuffers() function to visualize the continuous change.
Looks like you will want to create your own library from GLM. What you're doing in the code above no longer works. As another user stated, this is a good template to build functionality from. I'd suggest downloading GLM, taking it apart, and reverse engineering what you need into Python.
Below is minimal runnable example of what I believe to be a bug in kivy.
In this program, a scene is drawn to a FBO. The scene is then drawn with a glsl shader to apply a post-processing filter that makes everything grayscale.
When my post-processing widget is inside a ScreenManager, it only correctly draws the scene for a small number (3) of frames. It then draws nothing (BUG?). If it is not inside a ScreenManager, it runs perfectly.
I have narrowed down the bug to a single line of offending code, which is where I modify the vertices of a very simple mesh drawn in the scene. THe first couple of times I modify the mesh it works correctly, but more than that, the FBO is blank. Any ideas about why that might be the case?
Inside the code are many comments to guide the reader, and just after the imports are a couple of constants that can be used to switch between having the program run in a minimal screen manager (where there is a bug) or outside of it, where you can see the program working as it should.
I have tried to make the code as clear and minimal as possible. It's still a bit large. Please ask if you have questions!
from math import pi,cos,sin,atan2,ceil,sqrt
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.floatlayout import FloatLayout
from kivy.graphics import *
from kivy.graphics.opengl import *
from kivy.graphics.shader import *
from kivy.logger import Logger
from kivy.clock import Clock
from kivy.uix.screenmanager import ScreenManager
from kivy.uix.screenmanager import Screen
from kivy.resources import resource_find
from kivy.uix.image import Image
from kivy.core.window import Window
from kivy.graphics.transformation import Matrix
### You can configure this script here to demonstrate how the Widget
### hierarchy affects the bug, and how the number of times that the
### Sprite's mesh is updated influences the bug.
### One of the following two lines should be commented out.
WIDGET_SETUP = 'NO_SCREEN_MANAGER' ## NO BUG
#WIDGET_SETUP = 'INSIDE_SCREEN_MANAGER' ## BUG !!!
## I used the constant below to identify the number of frames
## correctly drawn, when in the INSIDE_SCREEN_MANAGER
## configuration. If the following constant is set above this critical
## threshold (>3 on my laptop), then the PostProcessor ceases to draw
## *anything*, including the Rectangles that are part of a separate
## RenderContext. If it is set low enough, then you see the first
## couple of frames drawn. And then the view stops being updated, but
## continues to be displayed correctly.
## A value of '-1' does not limit the number of frames updated.
NUMBER_OF_FRAMES_TO_UPDATE_MESH_VERTICES = -1
## CONFUSINGlY, decreasing the step size (amount of time between
## iterations) makes it so that even the first frame is not drawn.
## Why would this matter? It seems to me like something is happening
## at the end of the ScreenManagers transition to the screen?? This
## is just a wild guess..
DT = 0.2 ## DT = 0.01 makes not even the first frame be drawn.
class Sprite(object) :
def __init__(self,postProcessor) :
self.iteration = 0
## render context for applying a shader to the rendering of
## this sprite's mesh
self.spriteRC = RenderContext(use_parent_projection=False)
self.r = 0.1 # width of square to draw
self.a = 0.0
## uniform values for shader
self.spriteRC['modelview_mat'] = postProcessor.projectionMatrix
self.spriteRC['window_size'] = [float(Window.width),float(Window.height)]
self.spriteRC['color'] = [1.,1.,1.,1.]
## set up texture
self.uvsize = 1.,1.
self.uvpos = 0.,0.
## compile shader ## this shader gives the color according to
## the texture coordinates..
self.spriteRC.shader.vs = """
/* Outputs to the fragment shader */
varying vec2 tex_coord0;
varying vec4 frag_color;
/* vertex attributes */
attribute vec2 vPosition;
attribute vec2 vTexCoords0;
/* uniform variables */
uniform mat4 modelview_mat;
uniform mat4 projection_mat;
void main() {
tex_coord0 = vTexCoords0;
frag_color = vec4(0.,0.,0.,0.); // I don't understand why this line is necessary, but it is.
gl_Position = projection_mat * modelview_mat * vec4(vPosition.xy, 0.0, 1.0);
}"""
self.spriteRC.shader.fs = """
/* Outputs from the vertex shader */
varying vec2 tex_coord0;
void main (void){
gl_FragColor = vec4(tex_coord0.x,tex_coord0.y,tex_coord0.x,1.0);
}"""
if not self.spriteRC.shader.success :
raise Exception('Effect shader compile failed.')
## set up mesh, and add it to the render context
self.mesh = Mesh(mode = 'triangles')
self.mesh.indices=range(6)
self.initializeVertices()
self.iterate(0.0)
self.spriteRC.add(self.mesh)
## add this sprite's render context to the fbo in the postprocessor
postProcessor.addSpriteRenderContext(self.spriteRC)
def initializeVertices(self) :
self.vs = [-self.r,-self.r, self.uvpos[0], self.uvpos[1],
-self.r,+self.r, self.uvpos[0], self.uvpos[1]+self.uvsize[1],
+self.r,-self.r, self.uvpos[0]+self.uvsize[0],self.uvpos[1],
+self.r,+self.r, self.uvpos[0]+self.uvsize[0],self.uvpos[1]+self.uvsize[1],
+self.r,-self.r, self.uvpos[0]+self.uvsize[0],self.uvpos[1],
-self.r,+self.r, self.uvpos[0], self.uvpos[1]+self.uvsize[1],
]
def updateVertices(self) :
""" Changes those parts of the mesh that need to be changed """
xr = cos(self.a)*self.r - sin(self.a)*self.r
yr = sin(self.a)*self.r + cos(self.a)*self.r
self.vs[0::4] = -xr,-yr,+yr,+xr,+yr,-yr
self.vs[1::4] = -yr,+xr,-xr,+yr,-xr,+xr
def iterate(self,dt) :
self.iteration += 1
self.a += 0.05
self.updateVertices()
if (NUMBER_OF_FRAMES_TO_UPDATE_MESH_VERTICES < 0 or
self.iteration < NUMBER_OF_FRAMES_TO_UPDATE_MESH_VERTICES) :
## the following line is what causes the post-processor's
## FBO to break, again only when 1. the post-processor is
## inside a ScreenManager, and after a certain number of
## frames have gone by (3-10 in my experience).
self.mesh.vertices=self.vs
class PostProcessor(Widget) :
def __init__(self, **kwargs):
Logger.debug('world.init()')
self.setupPostProcessorRenderContext()
## draw a colored rectangle on to the fbo. This is independent
## of the sprite RC and demonstrates that when the bug occurs,
## the FBO ceases to draw anything at all
self.rectangleRC = RenderContext(use_parent_projection=False)
self.rectangleRC['modelview_mat'] = self.projectionMatrix
self.rectangleRC.add(Color(0,1,0,0.5))
self.rectangleRC.add(Rectangle(pos=(0.0,0.0),size=(0.5,0.5)))
self.fbo.add(self.rectangleRC)
self.canvas = self.postProcessorRC
super(PostProcessor, self).__init__(**kwargs)
def setupPostProcessorRenderContext(self) :
"""This RenderContext is responsible ONLY for drawing the FBO (and
applying a postprocessing shader to it). Both the rectangleRC
and spriteRC draw directly to the FBO.
"""
self.postProcessorRC=RenderContext()
self.postProcessorRC.shader.vs = """
/* Outputs to the fragment shader */
varying vec4 frag_color;
varying vec2 tex_coord0;
/* vertex attributes */
attribute vec2 vPosition;
attribute vec2 vTexCoords0;
/* uniform variables */
uniform mat4 modelview_mat;
uniform mat4 projection_mat;
uniform vec4 color;
uniform float opacity;
void main (void) {
frag_color = color * vec4(1.0, 1.0, 1.0, opacity);
tex_coord0 = vTexCoords0;
gl_Position = projection_mat * modelview_mat * vec4(vPosition.xy, 0.0, 1.0);
}"""
self.postProcessorRC.shader.fs = """
/* Outputs from the vertex shader */
varying vec4 frag_color;
varying vec2 tex_coord0;
/* uniform texture samplers */
uniform sampler2D texture0;
uniform vec2 resolution;
uniform float time;
void main() {
vec4 rgb = texture2D(texture0, tex_coord0);
float c = (rgb.x + rgb.y + rgb.z) * 0.3333;
gl_FragColor = vec4(c,c,c, 1.0);
}"""
if not self.postProcessorRC.shader.success :
raise Exception('Effect shader compile failed.')
with self.postProcessorRC:
# create the fbo
self.fbo = Fbo(size=(float(Window.width), float(Window.height)))
# show our fbo on the PostProcessor Widget render context
## by drawing a rectangle covering most of screen, with
## the texture set to self.fbo
Color(1, 1, 1)
Rectangle(pos=(-0.9,-0.9),
size=(1.8,1.8),
texture=self.fbo.texture)
## the following linear algebra sets the screen up nicely, so
## that the largest window dimension (width or height) ranges
## between -1 and 1, and the smaller dimension ranges between
## smallerDimSize/largerDimSize. This makes a square e.g. 0,0;
## 0,1; 1,1; 1,0 render as a square (rather than a rectangle).
self.projectionMatrix = Matrix()
self.projectionMatrix.look_at(0.,0.,1., # eye position coords
0.,0.,0., # looking at these coords
0,1.,0) # a vector that points up
if Window.height > Window.width :
self.xRadius = float(Window.width)/Window.height
self.yRadius = 1.0
self.projectionMatrix.scale(1.0/self.xRadius,1.0,1.0)
else :
self.xRadius = 1.0
self.yRadius = float(Window.height)/Window.width
self.projectionMatrix.scale(1.0,1.0/self.yRadius,1.0)
def addSpriteRenderContext(self,spriteRC) :
""" Add the sprite's render context to the FBO."""
self.fbo.add(spriteRC)
def iterate(self,dt) :
""" Clear the FBO every iteration. """
self.fbo.bind()
self.fbo.clear_buffer()
self.fbo.release()
class TestApp(App):
def build(self):
## Initialise the post processor, which should make monochrome
## and draw all things that draw to its FBO.
w = PostProcessor()
## initialize the sprite which should draw a rotating texture
## to the FBO in the post-processor.
s = Sprite(w)
## update the sprite to have its vertices rotate
def iterate(dt):
w.iterate(dt)
s.iterate(dt)
Clock.schedule_interval(iterate, DT)
## create a FLoatLayout that contains the post-processor
fl = FloatLayout()
fl.add_widget(w)
## Widget Setup
if WIDGET_SETUP == 'INSIDE_SCREEN_MANAGER' :
sm = ScreenManager()
scr = Screen(name='Screen 1')
sm.add_widget(scr)
scr.add_widget(fl)
return sm
else :
return fl
if __name__ == '__main__':
TestApp().run()
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions asking us to recommend or find a tool, library or favorite off-site resource are off-topic for Stack Overflow as they tend to attract opinionated answers and spam. Instead, describe the problem and what has been done so far to solve it.
Closed 9 years ago.
Improve this question
I am looking for a simple modern OpenGL (3.2+)example in Python.
I tried with GLUT and freeGLUT, but I am not able to get a 3.2 context on OS X (Mavericks). (This seems to be a known issue with GLUT/freeGLUT).
GLFW seems to be a modern lightweight alternative to GLUT, but it doesn't seem to have an official Python binding, and I could not find a simple example that uses 3.2 core profile features of OpenGL with GLFW and Python.
(I struggled with this problem, and so it could be useful for others, I am answering below as per SO guidelines.)
The code below uses PyOpenGL, PIL (for textures), numpy, GLFW and the corresponding Python binding cyglfw3.
Here is a screenshot of the output:
The main code is appended below. It uses some utility methods from a file called glutils.py (for loading texture, compiling shaders, etc.) which you can find here:
https://github.com/electronut/pp/tree/master/simplegl
Code listing follows:
import OpenGL
from OpenGL.GL import *
from OpenGL.GLUT import *
import numpy, math, sys, os
import glutils
import cyglfw3 as glfw
strVS = """
#version 330 core
layout(location = 0) in vec3 aVert;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
uniform vec4 uColor;
uniform float uTheta;
out vec4 vCol;
out vec2 vTexCoord;
void main() {
// rotational transform
mat4 rot = mat4(
vec4( cos(uTheta), sin(uTheta), 0.0, 0.0),
vec4(-sin(uTheta), cos(uTheta), 0.0, 0.0),
vec4(0.0, 0.0, 1.0, 0.0),
vec4(0.0, 0.0, 0.0, 1.0)
);
// transform vertex
gl_Position = uPMatrix * uMVMatrix * rot * vec4(aVert, 1.0);
// set color
vCol = vec4(uColor.rgb, 1.0);
// set texture coord
vTexCoord = aVert.xy + vec2(0.5, 0.5);
}
"""
strFS = """
#version 330 core
in vec4 vCol;
in vec2 vTexCoord;
uniform sampler2D tex2D;
uniform bool showCircle;
out vec4 fragColor;
void main() {
if (showCircle) {
// discard fragment outside circle
if (distance(vTexCoord, vec2(0.5, 0.5)) > 0.5) {
discard;
}
else {
fragColor = texture(tex2D, vTexCoord);
}
}
else {
fragColor = texture(tex2D, vTexCoord);
}
}
"""
class Scene:
""" OpenGL 3D scene class"""
# initialization
def __init__(self):
# create shader
self.program = glutils.loadShaders(strVS, strFS)
glUseProgram(self.program)
self.pMatrixUniform = glGetUniformLocation(self.program,
'uPMatrix')
self.mvMatrixUniform = glGetUniformLocation(self.program,
"uMVMatrix")
self.colorU = glGetUniformLocation(self.program, "uColor")
# color
self.col0 = [1.0, 0.0, 0.0, 1.0]
# texture
self.tex2D = glGetUniformLocation(self.program, "tex2D")
# define quad vertices
quadV = [
-0.5, -0.5, 0.0,
0.5, -0.5, 0.0,
-0.5, 0.5, 0.0,
0.5, 0.5, 0.0
]
# set up vertex array object (VAO)
self.vao = glGenVertexArrays(1)
glBindVertexArray(self.vao)
# vertices
self.vertexBuffer = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, self.vertexBuffer)
vertexData = numpy.array(quadV, numpy.float32)
glBufferData(GL_ARRAY_BUFFER, 4*len(vertexData), vertexData,
GL_STATIC_DRAW)
# enable vertex array
glEnableVertexAttribArray(0)
# set buffer data
glBindBuffer(GL_ARRAY_BUFFER, self.vertexBuffer)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, None)
# unbind VAO
glBindVertexArray(0)
# time
self.t = 0
# texture
self.texId = glutils.loadTexture('test.png')
# show circle?
self.showCircle = False
# step
def step(self):
# increment angle
self.t = (self.t + 1) % 360
# set shader angle in radians
glUniform1f(glGetUniformLocation(self.program, 'uTheta'),
math.radians(self.t))
# render
def render(self, pMatrix, mvMatrix):
# use shader
glUseProgram(self.program)
# set proj matrix
glUniformMatrix4fv(self.pMatrixUniform, 1, GL_FALSE, pMatrix)
# set modelview matrix
glUniformMatrix4fv(self.mvMatrixUniform, 1, GL_FALSE, mvMatrix)
# show circle?
glUniform1i(glGetUniformLocation(self.program, 'showCircle'),
self.showCircle)
# enable texture
glActiveTexture(GL_TEXTURE0)
glBindTexture(GL_TEXTURE_2D, self.texId)
glUniform1i(self.tex2D, 0)
# bind VAO
glBindVertexArray(self.vao)
# draw
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)
# unbind VAO
glBindVertexArray(0)
class RenderWindow:
"""GLFW Rendering window class"""
def __init__(self):
# save current working directory
cwd = os.getcwd()
# initialize glfw - this changes cwd
glfw.Init()
# restore cwd
os.chdir(cwd)
# version hints
glfw.WindowHint(glfw.CONTEXT_VERSION_MAJOR, 3)
glfw.WindowHint(glfw.CONTEXT_VERSION_MINOR, 3)
glfw.WindowHint(glfw.OPENGL_FORWARD_COMPAT, GL_TRUE)
glfw.WindowHint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
# make a window
self.width, self.height = 640, 480
self.aspect = self.width/float(self.height)
self.win = glfw.CreateWindow(self.width, self.height, "test")
# make context current
glfw.MakeContextCurrent(self.win)
# initialize GL
glViewport(0, 0, self.width, self.height)
glEnable(GL_DEPTH_TEST)
glClearColor(0.5, 0.5, 0.5,1.0)
# set window callbacks
glfw.SetMouseButtonCallback(self.win, self.onMouseButton)
glfw.SetKeyCallback(self.win, self.onKeyboard)
glfw.SetWindowSizeCallback(self.win, self.onSize)
# create 3D
self.scene = Scene()
# exit flag
self.exitNow = False
def onMouseButton(self, win, button, action, mods):
#print 'mouse button: ', win, button, action, mods
pass
def onKeyboard(self, win, key, scancode, action, mods):
#print 'keyboard: ', win, key, scancode, action, mods
if action == glfw.PRESS:
# ESC to quit
if key == glfw.KEY_ESCAPE:
self.exitNow = True
else:
# toggle cut
self.scene.showCircle = not self.scene.showCircle
def onSize(self, win, width, height):
#print 'onsize: ', win, width, height
self.width = width
self.height = height
self.aspect = width/float(height)
glViewport(0, 0, self.width, self.height)
def run(self):
# initializer timer
glfw.SetTime(0.0)
t = 0.0
while not glfw.WindowShouldClose(self.win) and not self.exitNow:
# update every x seconds
currT = glfw.GetTime()
if currT - t > 0.1:
# update time
t = currT
# clear
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
# build projection matrix
pMatrix = glutils.perspective(45.0, self.aspect, 0.1, 100.0)
mvMatrix = glutils.lookAt([0.0, 0.0, -2.0], [0.0, 0.0, 0.0],
[0.0, 1.0, 0.0])
# render
self.scene.render(pMatrix, mvMatrix)
# step
self.scene.step()
glfw.SwapBuffers(self.win)
# Poll for and process events
glfw.PollEvents()
# end
glfw.Terminate()
# main() function
def main():
print 'starting simpleglfw...'
rw = RenderWindow()
rw.run()
# call main
if __name__ == '__main__':
main()