diff options
Diffstat (limited to 'examples/opengl/hellogl2')
-rw-r--r-- | examples/opengl/hellogl2/doc/hellogl2.rst | 14 | ||||
-rw-r--r-- | examples/opengl/hellogl2/glwidget.py | 272 | ||||
-rw-r--r-- | examples/opengl/hellogl2/hellogl2.py | 489 | ||||
-rw-r--r-- | examples/opengl/hellogl2/hellogl2.pyproject | 2 | ||||
-rw-r--r-- | examples/opengl/hellogl2/logo.py | 101 | ||||
-rw-r--r-- | examples/opengl/hellogl2/main.py | 58 | ||||
-rw-r--r-- | examples/opengl/hellogl2/mainwindow.py | 29 | ||||
-rw-r--r-- | examples/opengl/hellogl2/window.py | 110 |
8 files changed, 585 insertions, 490 deletions
diff --git a/examples/opengl/hellogl2/doc/hellogl2.rst b/examples/opengl/hellogl2/doc/hellogl2.rst index 1223e138c..3471ebf30 100644 --- a/examples/opengl/hellogl2/doc/hellogl2.rst +++ b/examples/opengl/hellogl2/doc/hellogl2.rst @@ -4,6 +4,20 @@ Hello GL2 Example The Hello GL2 example demonstrates the basic use of the OpenGL-related classes provided with Qt. +In this example the widget's corresponding top-level window can change several +times during the widget's lifetime. Whenever this happens, the QOpenGLWidget's +associated context is destroyed and a new one is created, requiring us to clean +up the GL resources. + +The equivalent C++ example does this cleanup on emission of the +QOpenGLContext.aboutToBeDestroyed() signal. However, in Qt for Python, we +cannot rely on this signal when it is emitted from the destructor. + +Therefore, we do the cleanup in GLWidget.hideEvent(). + +This will be followed by an invocation of initializeGL() where we can recreate +all resources. + .. image:: hellogl2.png :width: 400 :alt: Hello GL2 Screenshot diff --git a/examples/opengl/hellogl2/glwidget.py b/examples/opengl/hellogl2/glwidget.py new file mode 100644 index 000000000..bbf200a6b --- /dev/null +++ b/examples/opengl/hellogl2/glwidget.py @@ -0,0 +1,272 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# Copyright (C) 2013 Riverbank Computing Limited. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import ctypes +from PySide6.QtCore import Signal, Slot, Qt, QSize, QPointF +from PySide6.QtGui import (QVector3D, QOpenGLFunctions, + QMatrix4x4, QOpenGLContext, QSurfaceFormat) +from PySide6.QtOpenGL import (QOpenGLVertexArrayObject, QOpenGLBuffer, + QOpenGLShaderProgram, QOpenGLShader) +from PySide6.QtOpenGLWidgets import QOpenGLWidget + +from OpenGL import GL + +from shiboken6 import VoidPtr +from logo import Logo + +FRAGMENT_SHADER_SOURCE_CORE = """#version 150 +in highp vec3 vert; +in highp vec3 vertNormal; +out highp vec4 fragColor; +uniform highp vec3 lightPos; +void main() { + highp vec3 L = normalize(lightPos - vert); + highp float NL = max(dot(normalize(vertNormal), L), 0.0); + highp vec3 color = vec3(0.39, 1.0, 0.0); + highp vec3 col = clamp(color * 0.2 + color * 0.8 * NL, 0.0, 1.0); + fragColor = vec4(col, 1.0); +}""" + + +FRAGMENT_SHADER_SOURCE = """varying highp vec3 vert; +varying highp vec3 vertNormal; +uniform highp vec3 lightPos; +void main() { + highp vec3 L = normalize(lightPos - vert); + highp float NL = max(dot(normalize(vertNormal), L), 0.0); + highp vec3 color = vec3(0.39, 1.0, 0.0); + highp vec3 col = clamp(color * 0.2 + color * 0.8 * NL, 0.0, 1.0); + gl_FragColor = vec4(col, 1.0); +}""" + + +VERTEX_SHADER_SOURCE_CORE = """#version 150 +in vec4 vertex; +in vec3 normal; +out vec3 vert; +out vec3 vertNormal; +uniform mat4 projMatrix; +uniform mat4 mvMatrix; +uniform mat3 normalMatrix; +void main() { + vert = vertex.xyz; + vertNormal = normalMatrix * normal; + gl_Position = projMatrix * mvMatrix * vertex; +}""" + + +VERTEX_SHADER_SOURCE = """attribute vec4 vertex; +attribute vec3 normal; +varying vec3 vert; +varying vec3 vertNormal; +uniform mat4 projMatrix; +uniform mat4 mvMatrix; +uniform mat3 normalMatrix; +void main() { + vert = vertex.xyz; + vertNormal = normalMatrix * normal; + gl_Position = projMatrix * mvMatrix * vertex; +}""" + + +class GLWidget(QOpenGLWidget, QOpenGLFunctions): + x_rotation_changed = Signal(int) + y_rotation_changed = Signal(int) + z_rotation_changed = Signal(int) + + _transparent = False + + def __init__(self, parent=None): + QOpenGLWidget.__init__(self, parent) + QOpenGLFunctions.__init__(self) + + self._core = QSurfaceFormat.defaultFormat().profile() == QSurfaceFormat.CoreProfile + + self._x_rot = 0 + self._y_rot = 0 + self._z_rot = 0 + self._last_pos = QPointF() + self.logo = Logo() + self.vao = QOpenGLVertexArrayObject() + self._logo_vbo = QOpenGLBuffer() + self.program = QOpenGLShaderProgram() + self._proj_matrix_loc = 0 + self._mv_matrix_loc = 0 + self._normal_matrix_loc = 0 + self._light_pos_loc = 0 + self.proj = QMatrix4x4() + self.camera = QMatrix4x4() + self.world = QMatrix4x4() + if self._transparent: + fmt = self.format() + fmt.setAlphaBufferSize(8) + self.setFormat(fmt) + + @staticmethod + def set_transparent(t): + GLWidget._transparent = t + + @staticmethod + def is_transparent(): + return GLWidget._transparent + + def x_rotation(self): + return self._x_rot + + def y_rotation(self): + return self._y_rot + + def z_rotation(self): + return self._z_rot + + def minimumSizeHint(self): + return QSize(50, 50) + + def sizeHint(self): + return QSize(400, 400) + + def normalize_angle(self, angle): + while angle < 0: + angle += 360 * 16 + while angle > 360 * 16: + angle -= 360 * 16 + return angle + + @Slot(int) + def set_xrotation(self, angle): + angle = self.normalize_angle(angle) + if angle != self._x_rot: + self._x_rot = angle + self.x_rotation_changed.emit(angle) + self.update() + + @Slot(int) + def set_yrotation(self, angle): + angle = self.normalize_angle(angle) + if angle != self._y_rot: + self._y_rot = angle + self.y_rotation_changed.emit(angle) + self.update() + + @Slot(int) + def set_zrotation(self, angle): + angle = self.normalize_angle(angle) + if angle != self._z_rot: + self._z_rot = angle + self.z_rotation_changed.emit(angle) + self.update() + + @Slot() + def cleanup(self): + if self.program: + self.makeCurrent() + self._logo_vbo.destroy() + del self.program + self.program = None + self.doneCurrent() + + def initializeGL(self): + self.initializeOpenGLFunctions() + self.glClearColor(0, 0, 0, 0 if self._transparent else 1) + + self.program = QOpenGLShaderProgram() + + if self._core: + self._vertex_shader = VERTEX_SHADER_SOURCE_CORE + self._fragment_shader = FRAGMENT_SHADER_SOURCE_CORE + else: + self._vertex_shader = VERTEX_SHADER_SOURCE + self._fragment_shader = FRAGMENT_SHADER_SOURCE + + self.program.addShaderFromSourceCode(QOpenGLShader.Vertex, + self._vertex_shader) + self.program.addShaderFromSourceCode(QOpenGLShader.Fragment, + self._fragment_shader) + self.program.bindAttributeLocation("vertex", 0) + self.program.bindAttributeLocation("normal", 1) + self.program.link() + + self.program.bind() + self._proj_matrix_loc = self.program.uniformLocation("projMatrix") + self._mv_matrix_loc = self.program.uniformLocation("mvMatrix") + self._normal_matrix_loc = self.program.uniformLocation("normalMatrix") + self._light_pos_loc = self.program.uniformLocation("lightPos") + + self.vao.create() + with QOpenGLVertexArrayObject.Binder(self.vao): + self._logo_vbo.create() + self._logo_vbo.bind() + float_size = ctypes.sizeof(ctypes.c_float) + self._logo_vbo.allocate(self.logo.const_data(), + self.logo.count() * float_size) + + self.setup_vertex_attribs() + + self.camera.setToIdentity() + self.camera.translate(0, 0, -1) + + self.program.setUniformValue(self._light_pos_loc, + QVector3D(0, 0, 70)) + self.program.release() + + def setup_vertex_attribs(self): + self._logo_vbo.bind() + f = QOpenGLContext.currentContext().functions() + f.glEnableVertexAttribArray(0) + f.glEnableVertexAttribArray(1) + float_size = ctypes.sizeof(ctypes.c_float) + + null = VoidPtr(0) + pointer = VoidPtr(3 * float_size) + f.glVertexAttribPointer(0, 3, int(GL.GL_FLOAT), int(GL.GL_FALSE), + 6 * float_size, null) + f.glVertexAttribPointer(1, 3, int(GL.GL_FLOAT), int(GL.GL_FALSE), + 6 * float_size, pointer) + self._logo_vbo.release() + + def paintGL(self): + self.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) + self.glEnable(GL.GL_DEPTH_TEST) + self.glEnable(GL.GL_CULL_FACE) + + self.world.setToIdentity() + self.world.rotate(180 - (self._x_rot / 16), 1, 0, 0) + self.world.rotate(self._y_rot / 16, 0, 1, 0) + self.world.rotate(self._z_rot / 16, 0, 0, 1) + + with QOpenGLVertexArrayObject.Binder(self.vao): + self.program.bind() + self.program.setUniformValue(self._proj_matrix_loc, self.proj) + self.program.setUniformValue(self._mv_matrix_loc, + self.camera * self.world) + normal_matrix = self.world.normalMatrix() + self.program.setUniformValue(self._normal_matrix_loc, normal_matrix) + + self.glDrawArrays(GL.GL_TRIANGLES, 0, self.logo.vertex_count()) + self.program.release() + + def resizeGL(self, width, height): + self.proj.setToIdentity() + self.proj.perspective(45, width / height, 0.01, 100) + + def hideEvent(self, event): + self.cleanup() + super().hideEvent(event) + + def mousePressEvent(self, event): + self._last_pos = event.position() + + def mouseMoveEvent(self, event): + pos = event.position() + dx = pos.x() - self._last_pos.x() + dy = pos.y() - self._last_pos.y() + + if event.buttons() & Qt.LeftButton: + self.set_xrotation(self._x_rot + 8 * dy) + self.set_yrotation(self._y_rot + 8 * dx) + elif event.buttons() & Qt.RightButton: + self.set_xrotation(self._x_rot + 8 * dy) + self.set_zrotation(self._z_rot + 8 * dx) + + self._last_pos = pos diff --git a/examples/opengl/hellogl2/hellogl2.py b/examples/opengl/hellogl2/hellogl2.py deleted file mode 100644 index 2f170dc90..000000000 --- a/examples/opengl/hellogl2/hellogl2.py +++ /dev/null @@ -1,489 +0,0 @@ - -############################################################################ -## -## Copyright (C) 2013 Riverbank Computing Limited. -## Copyright (C) 2021 The Qt Company Ltd. -## Contact: http://www.qt.io/licensing/ -## -## This file is part of the Qt for Python examples of the Qt Toolkit. -## -## $QT_BEGIN_LICENSE:BSD$ -## You may use this file under the terms of the BSD license as follows: -## -## "Redistribution and use in source and binary forms, with or without -## modification, are permitted provided that the following conditions are -## met: -## * Redistributions of source code must retain the above copyright -## notice, this list of conditions and the following disclaimer. -## * Redistributions in binary form must reproduce the above copyright -## notice, this list of conditions and the following disclaimer in -## the documentation and/or other materials provided with the -## distribution. -## * Neither the name of The Qt Company Ltd nor the names of its -## contributors may be used to endorse or promote products derived -## from this software without specific prior written permission. -## -## -## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -## -## $QT_END_LICENSE$ -## -############################################################################ - -"""PySide6 port of the opengl/hellogl2 example from Qt v5.x""" - -from argparse import ArgumentParser, RawTextHelpFormatter -import ctypes -import math -import numpy -import sys -from PySide6.QtCore import QCoreApplication, Signal, SIGNAL, SLOT, Qt, QSize, QPointF -from PySide6.QtGui import (QVector3D, QOpenGLFunctions, - QMatrix4x4, QOpenGLContext, QSurfaceFormat) -from PySide6.QtOpenGL import (QOpenGLVertexArrayObject, QOpenGLBuffer, - QOpenGLShaderProgram, QOpenGLShader) -from PySide6.QtWidgets import (QApplication, QWidget, QMessageBox, QHBoxLayout, - QSlider) -from PySide6.QtOpenGLWidgets import QOpenGLWidget - -from shiboken6 import VoidPtr - -try: - from OpenGL import GL -except ImportError: - app = QApplication(sys.argv) - message_box = QMessageBox(QMessageBox.Critical, "OpenGL hellogl", - "PyOpenGL must be installed to run this example.", - QMessageBox.Close) - message_box.setDetailedText("Run:\npip install PyOpenGL PyOpenGL_accelerate") - message_box.exec() - sys.exit(1) - - -class Window(QWidget): - def __init__(self, transparent, parent=None): - QWidget.__init__(self, parent) - - if transparent: - self.setAttribute(Qt.WA_TranslucentBackground) - self.setAttribute(Qt.WA_NoSystemBackground, False) - - self._gl_widget = GLWidget(transparent) - - self._x_slider = self.create_slider() - self._x_slider.valueChanged.connect(self._gl_widget.set_xrotation) - self._gl_widget.x_rotation_changed.connect(self._x_slider.setValue) - - self._y_slider = self.create_slider() - self._y_slider.valueChanged.connect(self._gl_widget.set_yrotation) - self._gl_widget.y_rotation_changed.connect(self._y_slider.setValue) - - self._z_slider = self.create_slider() - self._z_slider.valueChanged.connect(self._gl_widget.set_zrotation) - self._gl_widget.z_rotation_changed.connect(self._z_slider.setValue) - - main_layout = QHBoxLayout() - main_layout.addWidget(self._gl_widget) - main_layout.addWidget(self._x_slider) - main_layout.addWidget(self._y_slider) - main_layout.addWidget(self._z_slider) - self.setLayout(main_layout) - - self._x_slider.setValue(15 * 16) - self._y_slider.setValue(345 * 16) - self._z_slider.setValue(0 * 16) - - self.setWindowTitle(self.tr("Hello GL")) - - def create_slider(self): - slider = QSlider(Qt.Vertical) - - slider.setRange(0, 360 * 16) - slider.setSingleStep(16) - slider.setPageStep(15 * 16) - slider.setTickInterval(15 * 16) - slider.setTickPosition(QSlider.TicksRight) - return slider - - def keyPressEvent(self, event): - if event.key() == Qt.Key_Escape: - self.close() - else: - super(Window, self).keyPressEvent(event) - - -class Logo(): - def __init__(self): - self.m_count = 0 - self.i = 0 - self.m_data = numpy.empty(2500 * 6, dtype=ctypes.c_float) - - x1 = +0.06 - y1 = -0.14 - x2 = +0.14 - y2 = -0.06 - x3 = +0.08 - y3 = +0.00 - x4 = +0.30 - y4 = +0.22 - - self.quad(x1, y1, x2, y2, y2, x2, y1, x1) - self.quad(x3, y3, x4, y4, y4, x4, y3, x3) - - self.extrude(x1, y1, x2, y2) - self.extrude(x2, y2, y2, x2) - self.extrude(y2, x2, y1, x1) - self.extrude(y1, x1, x1, y1) - self.extrude(x3, y3, x4, y4) - self.extrude(x4, y4, y4, x4) - self.extrude(y4, x4, y3, x3) - - NUM_SECTORS = 100 - - for i in range(NUM_SECTORS): - angle = (i * 2 * math.pi) / NUM_SECTORS - x5 = 0.30 * math.sin(angle) - y5 = 0.30 * math.cos(angle) - x6 = 0.20 * math.sin(angle) - y6 = 0.20 * math.cos(angle) - - angle = ((i + 1) * 2 * math.pi) / NUM_SECTORS - x7 = 0.20 * math.sin(angle) - y7 = 0.20 * math.cos(angle) - x8 = 0.30 * math.sin(angle) - y8 = 0.30 * math.cos(angle) - - self.quad(x5, y5, x6, y6, x7, y7, x8, y8) - - self.extrude(x6, y6, x7, y7) - self.extrude(x8, y8, x5, y5) - - def const_data(self): - return self.m_data.tobytes() - - def count(self): - return self.m_count - - def vertex_count(self): - return self.m_count / 6 - - def quad(self, x1, y1, x2, y2, x3, y3, x4, y4): - n = QVector3D.normal(QVector3D(x4 - x1, y4 - y1, 0), QVector3D(x2 - x1, y2 - y1, 0)) - - self.add(QVector3D(x1, y1, -0.05), n) - self.add(QVector3D(x4, y4, -0.05), n) - self.add(QVector3D(x2, y2, -0.05), n) - - self.add(QVector3D(x3, y3, -0.05), n) - self.add(QVector3D(x2, y2, -0.05), n) - self.add(QVector3D(x4, y4, -0.05), n) - - n = QVector3D.normal(QVector3D(x1 - x4, y1 - y4, 0), QVector3D(x2 - x4, y2 - y4, 0)) - - self.add(QVector3D(x4, y4, 0.05), n) - self.add(QVector3D(x1, y1, 0.05), n) - self.add(QVector3D(x2, y2, 0.05), n) - - self.add(QVector3D(x2, y2, 0.05), n) - self.add(QVector3D(x3, y3, 0.05), n) - self.add(QVector3D(x4, y4, 0.05), n) - - def extrude(self, x1, y1, x2, y2): - n = QVector3D.normal(QVector3D(0, 0, -0.1), QVector3D(x2 - x1, y2 - y1, 0)) - - self.add(QVector3D(x1, y1, 0.05), n) - self.add(QVector3D(x1, y1, -0.05), n) - self.add(QVector3D(x2, y2, 0.05), n) - - self.add(QVector3D(x2, y2, -0.05), n) - self.add(QVector3D(x2, y2, 0.05), n) - self.add(QVector3D(x1, y1, -0.05), n) - - def add(self, v, n): - self.m_data[self.i] = v.x() - self.i += 1 - self.m_data[self.i] = v.y() - self.i += 1 - self.m_data[self.i] = v.z() - self.i += 1 - self.m_data[self.i] = n.x() - self.i += 1 - self.m_data[self.i] = n.y() - self.i += 1 - self.m_data[self.i] = n.z() - self.i += 1 - self.m_count += 6 - - -class GLWidget(QOpenGLWidget, QOpenGLFunctions): - x_rotation_changed = Signal(int) - y_rotation_changed = Signal(int) - z_rotation_changed = Signal(int) - - def __init__(self, transparent, parent=None): - QOpenGLWidget.__init__(self, parent) - QOpenGLFunctions.__init__(self) - - self._transparent = transparent - self._core = QSurfaceFormat.defaultFormat().profile() == QSurfaceFormat.CoreProfile - - self._x_rot = 0 - self._y_rot = 0 - self._z_rot = 0 - self._last_pos = QPointF() - self.logo = Logo() - self.vao = QOpenGLVertexArrayObject() - self._logo_vbo = QOpenGLBuffer() - self.program = QOpenGLShaderProgram() - self._proj_matrix_loc = 0 - self._mv_matrix_loc = 0 - self._normal_matrix_loc = 0 - self._light_pos_loc = 0 - self.proj = QMatrix4x4() - self.camera = QMatrix4x4() - self.world = QMatrix4x4() - if transparent: - fmt = self.format() - fmt.setAlphaBufferSize(8) - self.setFormat(fmt) - - def x_rotation(self): - return self._x_rot - - def y_rotation(self): - return self._y_rot - - def z_rotation(self): - return self._z_rot - - def minimumSizeHint(self): - return QSize(50, 50) - - def sizeHint(self): - return QSize(400, 400) - - def normalize_angle(self, angle): - while angle < 0: - angle += 360 * 16 - while angle > 360 * 16: - angle -= 360 * 16 - return angle - - def set_xrotation(self, angle): - angle = self.normalize_angle(angle) - if angle != self._x_rot: - self._x_rot = angle - self.x_rotation_changed.emit(angle) - self.update() - - def set_yrotation(self, angle): - angle = self.normalize_angle(angle) - if angle != self._y_rot: - self._y_rot = angle - self.y_rotation_changed.emit(angle) - self.update() - - def set_zrotation(self, angle): - angle = self.normalize_angle(angle) - if angle != self._z_rot: - self._z_rot = angle - self.z_rotation_changed.emit(angle) - self.update() - - def cleanup(self): - self.makeCurrent() - self._logo_vbo.destroy() - del self.program - self.program = None - self.doneCurrent() - - def vertex_shader_source_core(self): - return """#version 150 - in vec4 vertex; - in vec3 normal; - out vec3 vert; - out vec3 vertNormal; - uniform mat4 projMatrix; - uniform mat4 mvMatrix; - uniform mat3 normalMatrix; - void main() { - vert = vertex.xyz; - vertNormal = normalMatrix * normal; - gl_Position = projMatrix * mvMatrix * vertex; - }""" - - def fragment_shader_source_core(self): - return """#version 150 - in highp vec3 vert; - in highp vec3 vertNormal; - out highp vec4 fragColor; - uniform highp vec3 lightPos; - void main() { - highp vec3 L = normalize(lightPos - vert); - highp float NL = max(dot(normalize(vertNormal), L), 0.0); - highp vec3 color = vec3(0.39, 1.0, 0.0); - highp vec3 col = clamp(color * 0.2 + color * 0.8 * NL, 0.0, 1.0); - fragColor = vec4(col, 1.0); - }""" - - def vertex_shader_source(self): - return """attribute vec4 vertex; - attribute vec3 normal; - varying vec3 vert; - varying vec3 vertNormal; - uniform mat4 projMatrix; - uniform mat4 mvMatrix; - uniform mat3 normalMatrix; - void main() { - vert = vertex.xyz; - vertNormal = normalMatrix * normal; - gl_Position = projMatrix * mvMatrix * vertex; - }""" - - def fragment_shader_source(self): - return """varying highp vec3 vert; - varying highp vec3 vertNormal; - uniform highp vec3 lightPos; - void main() { - highp vec3 L = normalize(lightPos - vert); - highp float NL = max(dot(normalize(vertNormal), L), 0.0); - highp vec3 color = vec3(0.39, 1.0, 0.0); - highp vec3 col = clamp(color * 0.2 + color * 0.8 * NL, 0.0, 1.0); - gl_FragColor = vec4(col, 1.0); - }""" - - def initializeGL(self): - self.context().aboutToBeDestroyed.connect(self.cleanup) - self.initializeOpenGLFunctions() - self.glClearColor(0, 0, 0, 0 if self._transparent else 1) - - self.program = QOpenGLShaderProgram() - - if self._core: - self._vertex_shader = self.vertex_shader_source_core() - self._fragment_shader = self.fragment_shader_source_core() - else: - self._vertex_shader = self.vertex_shader_source() - self._fragment_shader = self.fragment_shader_source() - - self.program.addShaderFromSourceCode(QOpenGLShader.Vertex, self._vertex_shader) - self.program.addShaderFromSourceCode(QOpenGLShader.Fragment, self._fragment_shader) - self.program.bindAttributeLocation("vertex", 0) - self.program.bindAttributeLocation("normal", 1) - self.program.link() - - self.program.bind() - self._proj_matrix_loc = self.program.uniformLocation("projMatrix") - self._mv_matrix_loc = self.program.uniformLocation("mvMatrix") - self._normal_matrix_loc = self.program.uniformLocation("normalMatrix") - self._light_pos_loc = self.program.uniformLocation("lightPos") - - self.vao.create() - vao_binder = QOpenGLVertexArrayObject.Binder(self.vao) - - self._logo_vbo.create() - self._logo_vbo.bind() - float_size = ctypes.sizeof(ctypes.c_float) - self._logo_vbo.allocate(self.logo.const_data(), self.logo.count() * float_size) - - self.setup_vertex_attribs() - - self.camera.setToIdentity() - self.camera.translate(0, 0, -1) - - self.program.setUniformValue(self._light_pos_loc, QVector3D(0, 0, 70)) - self.program.release() - vao_binder = None - - def setup_vertex_attribs(self): - self._logo_vbo.bind() - f = QOpenGLContext.currentContext().functions() - f.glEnableVertexAttribArray(0) - f.glEnableVertexAttribArray(1) - float_size = ctypes.sizeof(ctypes.c_float) - - null = VoidPtr(0) - pointer = VoidPtr(3 * float_size) - f.glVertexAttribPointer(0, 3, int(GL.GL_FLOAT), int(GL.GL_FALSE), 6 * float_size, null) - f.glVertexAttribPointer(1, 3, int(GL.GL_FLOAT), int(GL.GL_FALSE), 6 * float_size, pointer) - self._logo_vbo.release() - - def paintGL(self): - self.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) - self.glEnable(GL.GL_DEPTH_TEST) - self.glEnable(GL.GL_CULL_FACE) - - self.world.setToIdentity() - self.world.rotate(180 - (self._x_rot / 16), 1, 0, 0) - self.world.rotate(self._y_rot / 16, 0, 1, 0) - self.world.rotate(self._z_rot / 16, 0, 0, 1) - - vao_binder = QOpenGLVertexArrayObject.Binder(self.vao) - self.program.bind() - self.program.setUniformValue(self._proj_matrix_loc, self.proj) - self.program.setUniformValue(self._mv_matrix_loc, self.camera * self.world) - normal_matrix = self.world.normalMatrix() - self.program.setUniformValue(self._normal_matrix_loc, normal_matrix) - - self.glDrawArrays(GL.GL_TRIANGLES, 0, self.logo.vertex_count()) - self.program.release() - vao_binder = None - - def resizeGL(self, width, height): - self.proj.setToIdentity() - self.proj.perspective(45, width / height, 0.01, 100) - - def mousePressEvent(self, event): - self._last_pos = event.position() - - def mouseMoveEvent(self, event): - pos = event.position() - dx = pos.x() - self._last_pos.x() - dy = pos.y() - self._last_pos.y() - - if event.buttons() & Qt.LeftButton: - self.set_xrotation(self._x_rot + 8 * dy) - self.set_yrotation(self._y_rot + 8 * dx) - elif event.buttons() & Qt.RightButton: - self.set_xrotation(self._x_rot + 8 * dy) - self.set_zrotation(self._z_rot + 8 * dx) - - self._last_pos = pos - - -if __name__ == '__main__': - app = QApplication(sys.argv) - parser = ArgumentParser(description="hellogl2", formatter_class=RawTextHelpFormatter) - parser.add_argument('--multisample', '-m', action='store_true', - help='Use Multisampling') - parser.add_argument('--coreprofile', '-c', action='store_true', - help='Use Core Profile') - parser.add_argument('--transparent', '-t', action='store_true', - help='Transparent Windows') - options = parser.parse_args() - - fmt = QSurfaceFormat() - fmt.setDepthBufferSize(24) - if options.multisample: - fmt.setSamples(4) - if options.coreprofile: - fmt.setVersion(3, 2) - fmt.setProfile(QSurfaceFormat.CoreProfile) - QSurfaceFormat.setDefaultFormat(fmt) - - main_window = Window(options.transparent) - main_window.resize(main_window.sizeHint()) - main_window.show() - - res = app.exec() - sys.exit(res) diff --git a/examples/opengl/hellogl2/hellogl2.pyproject b/examples/opengl/hellogl2/hellogl2.pyproject index 331d835af..d85a139e4 100644 --- a/examples/opengl/hellogl2/hellogl2.pyproject +++ b/examples/opengl/hellogl2/hellogl2.pyproject @@ -1,3 +1,3 @@ { - "files": ["hellogl2.py"] + "files": ["main.py", "glwidget.py", "logo.py", "mainwindow.py", "window.py"] } diff --git a/examples/opengl/hellogl2/logo.py b/examples/opengl/hellogl2/logo.py new file mode 100644 index 000000000..c236a1ec9 --- /dev/null +++ b/examples/opengl/hellogl2/logo.py @@ -0,0 +1,101 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# Copyright (C) 2013 Riverbank Computing Limited. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import math + +from PySide6.QtGui import (QVector3D, QVector3DList) + + +class Logo(): + def __init__(self): + self.m_data = QVector3DList() + self.m_data.reserve(5000) + + x1 = +0.06 + y1 = -0.14 + x2 = +0.14 + y2 = -0.06 + x3 = +0.08 + y3 = +0.00 + x4 = +0.30 + y4 = +0.22 + + self.quad(x1, y1, x2, y2, y2, x2, y1, x1) + self.quad(x3, y3, x4, y4, y4, x4, y3, x3) + + self.extrude(x1, y1, x2, y2) + self.extrude(x2, y2, y2, x2) + self.extrude(y2, x2, y1, x1) + self.extrude(y1, x1, x1, y1) + self.extrude(x3, y3, x4, y4) + self.extrude(x4, y4, y4, x4) + self.extrude(y4, x4, y3, x3) + + NUM_SECTORS = 100 + + for i in range(NUM_SECTORS): + angle = (i * 2 * math.pi) / NUM_SECTORS + x5 = 0.30 * math.sin(angle) + y5 = 0.30 * math.cos(angle) + x6 = 0.20 * math.sin(angle) + y6 = 0.20 * math.cos(angle) + + angle = ((i + 1) * 2 * math.pi) / NUM_SECTORS + x7 = 0.20 * math.sin(angle) + y7 = 0.20 * math.cos(angle) + x8 = 0.30 * math.sin(angle) + y8 = 0.30 * math.cos(angle) + + self.quad(x5, y5, x6, y6, x7, y7, x8, y8) + + self.extrude(x6, y6, x7, y7) + self.extrude(x8, y8, x5, y5) + + def const_data(self): + return self.m_data.constData() + + def count(self): + return len(self.m_data) * 3 + + def vertex_count(self): + return self.count() / 6 + + def quad(self, x1, y1, x2, y2, x3, y3, x4, y4): + n = QVector3D.normal(QVector3D(x4 - x1, y4 - y1, 0), + QVector3D(x2 - x1, y2 - y1, 0)) + + self.add(QVector3D(x1, y1, -0.05), n) + self.add(QVector3D(x4, y4, -0.05), n) + self.add(QVector3D(x2, y2, -0.05), n) + + self.add(QVector3D(x3, y3, -0.05), n) + self.add(QVector3D(x2, y2, -0.05), n) + self.add(QVector3D(x4, y4, -0.05), n) + + n = QVector3D.normal(QVector3D(x1 - x4, y1 - y4, 0), + QVector3D(x2 - x4, y2 - y4, 0)) + + self.add(QVector3D(x4, y4, 0.05), n) + self.add(QVector3D(x1, y1, 0.05), n) + self.add(QVector3D(x2, y2, 0.05), n) + + self.add(QVector3D(x2, y2, 0.05), n) + self.add(QVector3D(x3, y3, 0.05), n) + self.add(QVector3D(x4, y4, 0.05), n) + + def extrude(self, x1, y1, x2, y2): + n = QVector3D.normal(QVector3D(0, 0, -0.1), + QVector3D(x2 - x1, y2 - y1, 0)) + + self.add(QVector3D(x1, y1, 0.05), n) + self.add(QVector3D(x1, y1, -0.05), n) + self.add(QVector3D(x2, y2, 0.05), n) + + self.add(QVector3D(x2, y2, -0.05), n) + self.add(QVector3D(x2, y2, 0.05), n) + self.add(QVector3D(x1, y1, -0.05), n) + + def add(self, v, n): + self.m_data.append(v) + self.m_data.append(n) diff --git a/examples/opengl/hellogl2/main.py b/examples/opengl/hellogl2/main.py new file mode 100644 index 000000000..c7eb78a82 --- /dev/null +++ b/examples/opengl/hellogl2/main.py @@ -0,0 +1,58 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# Copyright (C) 2013 Riverbank Computing Limited. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +"""PySide6 port of the opengl/hellogl2 example from Qt v6.x""" + +from argparse import ArgumentParser, RawTextHelpFormatter +import sys +from PySide6.QtCore import Qt +from PySide6.QtGui import QSurfaceFormat +from PySide6.QtWidgets import (QApplication, QMessageBox) + + +try: + from mainwindow import MainWindow + from glwidget import GLWidget +except ImportError: + app = QApplication(sys.argv) + message_box = QMessageBox(QMessageBox.Critical, "OpenGL hellogl", + "PyOpenGL must be installed to run this example.", + QMessageBox.Close) + message_box.setDetailedText("Run:\npip install PyOpenGL PyOpenGL_accelerate") + message_box.exec() + sys.exit(1) + + +if __name__ == '__main__': + app = QApplication(sys.argv) + parser = ArgumentParser(description="hellogl2", + formatter_class=RawTextHelpFormatter) + parser.add_argument('--multisample', '-m', action='store_true', + help='Use Multisampling') + parser.add_argument('--coreprofile', '-c', action='store_true', + help='Use Core Profile') + parser.add_argument('--transparent', '-t', action='store_true', + help='Transparent Windows') + options = parser.parse_args() + + fmt = QSurfaceFormat() + fmt.setDepthBufferSize(24) + if options.multisample: + fmt.setSamples(4) + if options.coreprofile: + fmt.setVersion(3, 2) + fmt.setProfile(QSurfaceFormat.CoreProfile) + QSurfaceFormat.setDefaultFormat(fmt) + + GLWidget.set_transparent(options.transparent) + + main_window = MainWindow() + if options.transparent: + main_window.setAttribute(Qt.WA_TranslucentBackground) + main_window.setAttribute(Qt.WA_NoSystemBackground, False) + + main_window.show() + + res = app.exec() + sys.exit(res) diff --git a/examples/opengl/hellogl2/mainwindow.py b/examples/opengl/hellogl2/mainwindow.py new file mode 100644 index 000000000..69b9b66fe --- /dev/null +++ b/examples/opengl/hellogl2/mainwindow.py @@ -0,0 +1,29 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtCore import Slot, Qt +from PySide6.QtGui import QKeySequence +from PySide6.QtWidgets import QMainWindow, QMessageBox + +from window import Window + + +class MainWindow(QMainWindow): + + def __init__(self): + super().__init__() + menuWindow = self.menuBar().addMenu("Window") + menuWindow.addAction("Add new", QKeySequence(Qt.CTRL | Qt.Key_N), + self.onAddNew) + menuWindow.addAction("Quit", QKeySequence(Qt.CTRL | Qt.Key_Q), + qApp.closeAllWindows) # noqa: F821 + + self.onAddNew() + + @Slot() + def onAddNew(self): + if not self.centralWidget(): + self.setCentralWidget(Window(self)) + else: + QMessageBox.information(self, "Cannot Add Window()", + "Already occupied. Undock first.") diff --git a/examples/opengl/hellogl2/window.py b/examples/opengl/hellogl2/window.py new file mode 100644 index 000000000..ad61d2f97 --- /dev/null +++ b/examples/opengl/hellogl2/window.py @@ -0,0 +1,110 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# Copyright (C) 2013 Riverbank Computing Limited. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtCore import Slot, Qt +from PySide6.QtWidgets import (QHBoxLayout, QMainWindow, + QMessageBox, QPushButton, QSlider, + QVBoxLayout, QWidget) + +from glwidget import GLWidget + + +def _main_window(): + for t in qApp.topLevelWidgets(): # noqa: F821 + if isinstance(t, QMainWindow): + return t + return None + + +class Window(QWidget): + instances = [] # Keep references when undocked + + def __init__(self, parent=None): + super().__init__(parent) + self.instances.append(self) + + self._gl_widget = GLWidget() + + self._x_slider = self.create_slider() + self._x_slider.valueChanged.connect(self._gl_widget.set_xrotation) + self._gl_widget.x_rotation_changed.connect(self._x_slider.setValue) + + self._y_slider = self.create_slider() + self._y_slider.valueChanged.connect(self._gl_widget.set_yrotation) + self._gl_widget.y_rotation_changed.connect(self._y_slider.setValue) + + self._z_slider = self.create_slider() + self._z_slider.valueChanged.connect(self._gl_widget.set_zrotation) + self._gl_widget.z_rotation_changed.connect(self._z_slider.setValue) + + mainLayout = QVBoxLayout(self) + w = QWidget() + container = QHBoxLayout(w) + container.addWidget(self._gl_widget) + container.addWidget(self._x_slider) + container.addWidget(self._y_slider) + container.addWidget(self._z_slider) + + mainLayout.addWidget(w) + self._dock_btn = QPushButton("Undock") + self._dock_btn.clicked.connect(self.dock_undock) + mainLayout.addWidget(self._dock_btn) + + self._x_slider.setValue(15 * 16) + self._y_slider.setValue(345 * 16) + self._z_slider.setValue(0 * 16) + + self.setWindowTitle(self.tr("Hello GL")) + + def create_slider(self): + slider = QSlider(Qt.Vertical) + + slider.setRange(0, 360 * 16) + slider.setSingleStep(16) + slider.setPageStep(15 * 16) + slider.setTickInterval(15 * 16) + slider.setTickPosition(QSlider.TicksRight) + return slider + + def closeEvent(self, event): + self.instances.remove(self) + event.accept() + + def keyPressEvent(self, event): + if self.isWindow() and event.key() == Qt.Key_Escape: + self.close() + else: + super().keyPressEvent(event) + + @Slot() + def dock_undock(self): + if self.parent(): + self.undock() + else: + self.dock() + + def dock(self): + mainWindow = _main_window() + if not mainWindow or not mainWindow.isVisible(): + QMessageBox.information(self, "Cannot Dock", + "Main window already closed") + return + if mainWindow.centralWidget(): + QMessageBox.information(self, "Cannot Dock", + "Main window already occupied") + return + + self.setAttribute(Qt.WA_DeleteOnClose, False) + self._dock_btn.setText("Undock") + mainWindow.setCentralWidget(self) + + def undock(self): + self.setParent(None) + self.setAttribute(Qt.WA_DeleteOnClose) + geometry = self.screen().availableGeometry() + x = geometry.x() + (geometry.width() - self.width()) / 2 + y = geometry.y() + (geometry.height() - self.height()) / 2 + self.move(x, y) + self._dock_btn.setText("Dock") + self.show() |