1#!/usr/bin/env python
2
3
4#############################################################################
5##
6## Copyright (C) 2018 Riverbank Computing Limited.
7## Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
8## All rights reserved.
9##
10## This file is part of the examples of PyQt.
11##
12## $QT_BEGIN_LICENSE:BSD$
13## You may use this file under the terms of the BSD license as follows:
14##
15## "Redistribution and use in source and binary forms, with or without
16## modification, are permitted provided that the following conditions are
17## met:
18##   * Redistributions of source code must retain the above copyright
19##     notice, this list of conditions and the following disclaimer.
20##   * Redistributions in binary form must reproduce the above copyright
21##     notice, this list of conditions and the following disclaimer in
22##     the documentation and/or other materials provided with the
23##     distribution.
24##   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
25##     the names of its contributors may be used to endorse or promote
26##     products derived from this software without specific prior written
27##     permission.
28##
29## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
32## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
33## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
34## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
36## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
38## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
39## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
40## $QT_END_LICENSE$
41##
42#############################################################################
43
44
45import sys
46import math
47
48from PyQt5.QtCore import pyqtSignal, QPoint, QSize, Qt
49from PyQt5.QtGui import QColor, QOpenGLVersionProfile
50from PyQt5.QtWidgets import (QApplication, QHBoxLayout, QOpenGLWidget, QSlider,
51        QWidget)
52
53
54class Window(QWidget):
55    def __init__(self):
56        super(Window, self).__init__()
57
58        self.glWidget = GLWidget()
59
60        self.xSlider = self.createSlider()
61        self.ySlider = self.createSlider()
62        self.zSlider = self.createSlider()
63
64        self.xSlider.valueChanged.connect(self.glWidget.setXRotation)
65        self.glWidget.xRotationChanged.connect(self.xSlider.setValue)
66        self.ySlider.valueChanged.connect(self.glWidget.setYRotation)
67        self.glWidget.yRotationChanged.connect(self.ySlider.setValue)
68        self.zSlider.valueChanged.connect(self.glWidget.setZRotation)
69        self.glWidget.zRotationChanged.connect(self.zSlider.setValue)
70
71        mainLayout = QHBoxLayout()
72        mainLayout.addWidget(self.glWidget)
73        mainLayout.addWidget(self.xSlider)
74        mainLayout.addWidget(self.ySlider)
75        mainLayout.addWidget(self.zSlider)
76        self.setLayout(mainLayout)
77
78        self.xSlider.setValue(15 * 16)
79        self.ySlider.setValue(345 * 16)
80        self.zSlider.setValue(0 * 16)
81
82        self.setWindowTitle("Hello GL")
83
84    def createSlider(self):
85        slider = QSlider(Qt.Vertical)
86
87        slider.setRange(0, 360 * 16)
88        slider.setSingleStep(16)
89        slider.setPageStep(15 * 16)
90        slider.setTickInterval(15 * 16)
91        slider.setTickPosition(QSlider.TicksRight)
92
93        return slider
94
95
96class GLWidget(QOpenGLWidget):
97    xRotationChanged = pyqtSignal(int)
98    yRotationChanged = pyqtSignal(int)
99    zRotationChanged = pyqtSignal(int)
100
101    def __init__(self, parent=None):
102        super(GLWidget, self).__init__(parent)
103
104        self.object = 0
105        self.xRot = 0
106        self.yRot = 0
107        self.zRot = 0
108
109        self.lastPos = QPoint()
110
111        self.trolltechGreen = QColor.fromCmykF(0.40, 0.0, 1.0, 0.0)
112        self.trolltechPurple = QColor.fromCmykF(0.39, 0.39, 0.0, 0.0)
113
114    def minimumSizeHint(self):
115        return QSize(50, 50)
116
117    def sizeHint(self):
118        return QSize(400, 400)
119
120    def setXRotation(self, angle):
121        angle = self.normalizeAngle(angle)
122        if angle != self.xRot:
123            self.xRot = angle
124            self.xRotationChanged.emit(angle)
125            self.update()
126
127    def setYRotation(self, angle):
128        angle = self.normalizeAngle(angle)
129        if angle != self.yRot:
130            self.yRot = angle
131            self.yRotationChanged.emit(angle)
132            self.update()
133
134    def setZRotation(self, angle):
135        angle = self.normalizeAngle(angle)
136        if angle != self.zRot:
137            self.zRot = angle
138            self.zRotationChanged.emit(angle)
139            self.update()
140
141    def initializeGL(self):
142        version_profile = QOpenGLVersionProfile()
143        version_profile.setVersion(2, 0)
144        self.gl = self.context().versionFunctions(version_profile)
145        self.gl.initializeOpenGLFunctions()
146
147        self.setClearColor(self.trolltechPurple.darker())
148        self.object = self.makeObject()
149        self.gl.glShadeModel(self.gl.GL_FLAT)
150        self.gl.glEnable(self.gl.GL_DEPTH_TEST)
151        self.gl.glEnable(self.gl.GL_CULL_FACE)
152
153    def paintGL(self):
154        self.gl.glClear(
155                self.gl.GL_COLOR_BUFFER_BIT | self.gl.GL_DEPTH_BUFFER_BIT)
156        self.gl.glLoadIdentity()
157        self.gl.glTranslated(0.0, 0.0, -10.0)
158        self.gl.glRotated(self.xRot / 16.0, 1.0, 0.0, 0.0)
159        self.gl.glRotated(self.yRot / 16.0, 0.0, 1.0, 0.0)
160        self.gl.glRotated(self.zRot / 16.0, 0.0, 0.0, 1.0)
161        self.gl.glCallList(self.object)
162
163    def resizeGL(self, width, height):
164        side = min(width, height)
165        if side < 0:
166            return
167
168        self.gl.glViewport((width - side) // 2, (height - side) // 2, side,
169                side)
170
171        self.gl.glMatrixMode(self.gl.GL_PROJECTION)
172        self.gl.glLoadIdentity()
173        self.gl.glOrtho(-0.5, +0.5, +0.5, -0.5, 4.0, 15.0)
174        self.gl.glMatrixMode(self.gl.GL_MODELVIEW)
175
176    def mousePressEvent(self, event):
177        self.lastPos = event.pos()
178
179    def mouseMoveEvent(self, event):
180        dx = event.x() - self.lastPos.x()
181        dy = event.y() - self.lastPos.y()
182
183        if event.buttons() & Qt.LeftButton:
184            self.setXRotation(self.xRot + 8 * dy)
185            self.setYRotation(self.yRot + 8 * dx)
186        elif event.buttons() & Qt.RightButton:
187            self.setXRotation(self.xRot + 8 * dy)
188            self.setZRotation(self.zRot + 8 * dx)
189
190        self.lastPos = event.pos()
191
192    def makeObject(self):
193        genList = self.gl.glGenLists(1)
194        self.gl.glNewList(genList, self.gl.GL_COMPILE)
195
196        self.gl.glBegin(self.gl.GL_QUADS)
197
198        x1 = +0.06
199        y1 = -0.14
200        x2 = +0.14
201        y2 = -0.06
202        x3 = +0.08
203        y3 = +0.00
204        x4 = +0.30
205        y4 = +0.22
206
207        self.quad(x1, y1, x2, y2, y2, x2, y1, x1)
208        self.quad(x3, y3, x4, y4, y4, x4, y3, x3)
209
210        self.extrude(x1, y1, x2, y2)
211        self.extrude(x2, y2, y2, x2)
212        self.extrude(y2, x2, y1, x1)
213        self.extrude(y1, x1, x1, y1)
214        self.extrude(x3, y3, x4, y4)
215        self.extrude(x4, y4, y4, x4)
216        self.extrude(y4, x4, y3, x3)
217
218        NumSectors = 200
219
220        for i in range(NumSectors):
221            angle1 = (i * 2 * math.pi) / NumSectors
222            x5 = 0.30 * math.sin(angle1)
223            y5 = 0.30 * math.cos(angle1)
224            x6 = 0.20 * math.sin(angle1)
225            y6 = 0.20 * math.cos(angle1)
226
227            angle2 = ((i + 1) * 2 * math.pi) / NumSectors
228            x7 = 0.20 * math.sin(angle2)
229            y7 = 0.20 * math.cos(angle2)
230            x8 = 0.30 * math.sin(angle2)
231            y8 = 0.30 * math.cos(angle2)
232
233            self.quad(x5, y5, x6, y6, x7, y7, x8, y8)
234
235            self.extrude(x6, y6, x7, y7)
236            self.extrude(x8, y8, x5, y5)
237
238        self.gl.glEnd()
239        self.gl.glEndList()
240
241        return genList
242
243    def quad(self, x1, y1, x2, y2, x3, y3, x4, y4):
244        self.setColor(self.trolltechGreen)
245
246        self.gl.glVertex3d(x1, y1, -0.05)
247        self.gl.glVertex3d(x2, y2, -0.05)
248        self.gl.glVertex3d(x3, y3, -0.05)
249        self.gl.glVertex3d(x4, y4, -0.05)
250
251        self.gl.glVertex3d(x4, y4, +0.05)
252        self.gl.glVertex3d(x3, y3, +0.05)
253        self.gl.glVertex3d(x2, y2, +0.05)
254        self.gl.glVertex3d(x1, y1, +0.05)
255
256    def extrude(self, x1, y1, x2, y2):
257        self.setColor(self.trolltechGreen.darker(250 + int(100 * x1)))
258
259        self.gl.glVertex3d(x1, y1, +0.05)
260        self.gl.glVertex3d(x2, y2, +0.05)
261        self.gl.glVertex3d(x2, y2, -0.05)
262        self.gl.glVertex3d(x1, y1, -0.05)
263
264    def normalizeAngle(self, angle):
265        while angle < 0:
266            angle += 360 * 16
267        while angle > 360 * 16:
268            angle -= 360 * 16
269        return angle
270
271    def setClearColor(self, c):
272        self.gl.glClearColor(c.redF(), c.greenF(), c.blueF(), c.alphaF())
273
274    def setColor(self, c):
275        self.gl.glColor4f(c.redF(), c.greenF(), c.blueF(), c.alphaF())
276
277
278if __name__ == '__main__':
279
280    app = QApplication(sys.argv)
281    window = Window()
282    window.show()
283    sys.exit(app.exec_())
284