From e158034c078076e61aa1a6712e856a52272a9713 Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Sat, 17 Jul 2021 09:49:51 +0800 Subject: [PATCH 01/25] remove devicePixelRatio argument. only needed for Qt4 --- pyqtgraph/opengl/GLViewWidget.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/pyqtgraph/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py index 7096b7b4..61ab2f61 100644 --- a/pyqtgraph/opengl/GLViewWidget.py +++ b/pyqtgraph/opengl/GLViewWidget.py @@ -21,10 +21,8 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): ================ ============================================================== **Arguments:** parent (QObject, optional): Parent QObject. Defaults to None. - devicePixelRatio (float, optional): High-DPI displays Qt5 should automatically - detect the correct resolution. For Qt4, specify the - ``devicePixelRatio`` argument when initializing the widget - (usually this value is 1-2). Defaults to None. + devicePixelRatio No longer in use. High-DPI displays should automatically + detect the correct resolution. rotationMethod (str): Mechanimsm to drive the rotation method, options are 'euler' and 'quaternion'. Defaults to 'euler'. ================ ============================================================== @@ -46,7 +44,6 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): 'azimuth': 45, ## camera's azimuthal angle in degrees ## (rotation around z-axis 0 points along x-axis) 'viewport': None, ## glViewport params; None == whole widget - 'devicePixelRatio': devicePixelRatio, 'rotationMethod': rotationMethod } self.reset() @@ -152,10 +149,6 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): return tuple([int(x * dpr) for x in vp]) def devicePixelRatio(self): - dpr = self.opts['devicePixelRatio'] - if dpr is not None: - return dpr - return self.devicePixelRatioF() def resizeGL(self, w, h): From ee9b1565bd4545b1835d3cc2e50be193c9483469 Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Sat, 17 Jul 2021 10:14:53 +0800 Subject: [PATCH 02/25] don't redefine width() and height() Qt widgets define width() and height() to be in device independent pixels. Don't change that meaning. --- pyqtgraph/opengl/GLViewWidget.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/pyqtgraph/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py index 61ab2f61..3d1f41d6 100644 --- a/pyqtgraph/opengl/GLViewWidget.py +++ b/pyqtgraph/opengl/GLViewWidget.py @@ -71,14 +71,13 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): window.screenChanged.connect(self._updateScreen) self._updateScreen(window.screen()) - def width(self): + def deviceWidth(self): dpr = self.devicePixelRatio() - return int(super().width() * dpr) - - def height(self): - dpr = self.devicePixelRatio() - return int(super().height() * dpr) + return int(self.width() * dpr) + def deviceHeight(self): + dpr = self.devicePixelRatio() + return int(self.height() * dpr) def reset(self): """ @@ -143,8 +142,10 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): def getViewport(self): vp = self.opts['viewport'] if vp is None: - return (0, 0, self.width(), self.height()) + return (0, 0, self.deviceWidth(), self.deviceHeight()) else: + # note: the following code means that we have defined opts['viewport'] + # to be in device independent pixels. dpr = self.devicePixelRatio() return tuple([int(x * dpr) for x in vp]) @@ -165,7 +166,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): def projectionMatrix(self, region=None): if region is None: - region = (0, 0, self.width(), self.height()) + region = (0, 0, self.deviceWidth(), self.deviceHeight()) x0, y0, w, h = self.getViewport() dist = self.opts['distance'] @@ -211,7 +212,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): Return a list of the items displayed in the region (x, y, w, h) relative to the widget. """ - region = (region[0], self.height()-(region[1]+region[3]), region[2], region[3]) + region = (region[0], self.deviceHeight()-(region[1]+region[3]), region[2], region[3]) #buf = np.zeros(100000, dtype=np.uint) buf = glSelectBuffer(100000) @@ -378,7 +379,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): cVec = self.opts['center'] - cPos dist = cVec.length() ## distance from camera to center xDist = dist * 2. * tan(0.5 * radians(self.opts['fov'])) ## approx. width of view at distance of center point - xScale = xDist / self.width() + xScale = xDist / self.deviceWidth() zVec = QtGui.QVector3D(0,0,1) xVec = QtGui.QVector3D.crossProduct(zVec, cVec).normalized() yVec = QtGui.QVector3D.crossProduct(xVec, zVec).normalized() @@ -403,7 +404,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): fov = radians(self.opts['fov']) dist = (self.opts['center'] - self.cameraPosition()).length() fov_factor = tan(fov / 2) * 2 - scale_factor = dist * fov_factor / self.width() + scale_factor = dist * fov_factor / self.deviceWidth() z = scale_factor * cos(elev) * dy x = scale_factor * (sin(azim) * dx - sin(elev) * cos(azim) * dy) y = scale_factor * (cos(azim) * dx + sin(elev) * sin(azim) * dy) @@ -425,7 +426,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): else: dist = (pos-cam).length() xDist = dist * 2. * tan(0.5 * radians(self.opts['fov'])) - return xDist / self.width() + return xDist / self.deviceWidth() def mousePressEvent(self, ev): lpos = ev.position() if hasattr(ev, 'position') else ev.localPos() @@ -543,8 +544,8 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): """ Read the current buffer pixels out as a QImage. """ - w = self.width() - h = self.height() + w = self.deviceWidth() + h = self.deviceHeight() self.repaint() pixels = np.empty((h, w, 4), dtype=np.ubyte) pixels[:] = 128 From 025ca08574924bb386b9e0298e15556639036fc5 Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Sat, 17 Jul 2021 10:17:31 +0800 Subject: [PATCH 03/25] delete empty resizeGL() --- pyqtgraph/opengl/GLViewWidget.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pyqtgraph/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py index 3d1f41d6..71768b6a 100644 --- a/pyqtgraph/opengl/GLViewWidget.py +++ b/pyqtgraph/opengl/GLViewWidget.py @@ -152,11 +152,6 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): def devicePixelRatio(self): return self.devicePixelRatioF() - def resizeGL(self, w, h): - pass - #glViewport(*self.getViewport()) - #self.update() - def setProjection(self, region=None): m = self.projectionMatrix(region) glMatrixMode(GL_PROJECTION) From 5d7dd101f2d70d3838431036ad91757706c6cb4b Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Sat, 17 Jul 2021 10:21:26 +0800 Subject: [PATCH 04/25] load matrix instead of multiplying to identity --- pyqtgraph/opengl/GLViewWidget.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/pyqtgraph/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py index 71768b6a..e80ae57b 100644 --- a/pyqtgraph/opengl/GLViewWidget.py +++ b/pyqtgraph/opengl/GLViewWidget.py @@ -155,9 +155,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): def setProjection(self, region=None): m = self.projectionMatrix(region) glMatrixMode(GL_PROJECTION) - glLoadIdentity() - a = np.array(m.copyDataTo()).reshape((4,4)) - glMultMatrixf(a.transpose()) + glLoadMatrixf(m.data()) def projectionMatrix(self, region=None): if region is None: @@ -183,11 +181,9 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): return tr def setModelview(self): - glMatrixMode(GL_MODELVIEW) - glLoadIdentity() m = self.viewMatrix() - a = np.array(m.copyDataTo()).reshape((4,4)) - glMultMatrixf(a.transpose()) + glMatrixMode(GL_MODELVIEW) + glLoadMatrixf(m.data()) def viewMatrix(self): tr = QtGui.QMatrix4x4() From 1b00d3448abdd6a3074c3180a60031aa18735793 Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Sat, 17 Jul 2021 10:28:52 +0800 Subject: [PATCH 05/25] reimplement readQImage() tested that call to repaint() is not needed --- pyqtgraph/opengl/GLViewWidget.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/pyqtgraph/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py index e80ae57b..c9113d44 100644 --- a/pyqtgraph/opengl/GLViewWidget.py +++ b/pyqtgraph/opengl/GLViewWidget.py @@ -535,20 +535,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): """ Read the current buffer pixels out as a QImage. """ - w = self.deviceWidth() - h = self.deviceHeight() - self.repaint() - pixels = np.empty((h, w, 4), dtype=np.ubyte) - pixels[:] = 128 - pixels[...,0] = 50 - pixels[...,3] = 255 - - glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixels) - - pixels = pixels[::-1].copy() # flip vertical - - qimg = fn.ndarray_to_qimage(pixels, QtGui.QImage.Format.Format_RGBA8888) - return qimg + return self.grabFramebuffer() def renderToArray(self, size, format=GL_BGRA, type=GL_UNSIGNED_BYTE, textureSize=1024, padding=256): w,h = map(int, size) From b5fc3d2a7e4b3e8c9fbb50bae1d7840da0db9234 Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Sat, 17 Jul 2021 10:40:27 +0800 Subject: [PATCH 06/25] add comment about definition of viewport --- pyqtgraph/opengl/GLViewWidget.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyqtgraph/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py index c9113d44..7bf09f4d 100644 --- a/pyqtgraph/opengl/GLViewWidget.py +++ b/pyqtgraph/opengl/GLViewWidget.py @@ -230,6 +230,8 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): if viewport is None: glViewport(*self.getViewport()) else: + # note: the following code means that we have defined "viewport" + # to be in device pixels. glViewport(*viewport) self.setProjection(region=region) self.setModelview() From ce4c6d95edc66e0aec2609ded392ec2287a76704 Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Sat, 17 Jul 2021 11:03:18 +0800 Subject: [PATCH 07/25] fix setCameraPosition not setting ele and azi in euler mode --- pyqtgraph/opengl/GLViewWidget.py | 37 ++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/pyqtgraph/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py index 7bf09f4d..5a6d4dda 100644 --- a/pyqtgraph/opengl/GLViewWidget.py +++ b/pyqtgraph/opengl/GLViewWidget.py @@ -284,21 +284,40 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): glPopMatrix() def setCameraPosition(self, pos=None, distance=None, elevation=None, azimuth=None, rotation=None): + if rotation is not None: + # Alternatively, we could define that rotation overrides elevation and azimuth + if elevation is not None: + raise ValueError("cannot set both rotation and elevation") + if azimuth is not None: + raise ValueError("cannot set both rotation and azimuth") + if pos is not None: self.opts['center'] = pos if distance is not None: self.opts['distance'] = distance - if rotation is not None: - # set with quaternion - self.opts['rotation'] = rotation + + if self.opts['rotationMethod'] == "quaternion": + # note that "quaternion" mode modifies only opts['rotation'] + if elevation is not None or azimuth is not None: + eu = self.opts['rotation'].toEulerAngles() + if azimuth is not None: + eu.setZ(-azimuth-90) + if elevation is not None: + eu.setX(elevation-90) + self.opts['rotation'] = QtGui.QQuaternion.fromEulerAngles(eu) + if rotation is not None: + self.opts['rotation'] = rotation else: - # set with elevation-azimuth, restored for compatibility - eu = self.opts['rotation'].toEulerAngles() - if azimuth is not None: - eu.setZ(-azimuth-90) + # note that "euler" mode modifies only opts['elevation'] and opts['azimuth'] if elevation is not None: - eu.setX(elevation-90) - self.opts['rotation'] = QtGui.QQuaternion.fromEulerAngles(eu) + self.opts['elevation'] = elevation + if azimuth is not None: + self.opts['azimuth'] = azimuth + if rotation is not None: + eu = rotation.toEulerAngles() + self.opts['elevation'] = eu.x() + 90 + self.opts['azimuth'] = -eu.z() - 90 + self.update() def cameraPosition(self): From f698ccc06ebc02c36a7632ccca44ff416993d6c8 Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Sat, 17 Jul 2021 13:05:40 +0800 Subject: [PATCH 08/25] load background color from configOption --- pyqtgraph/opengl/GLViewWidget.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyqtgraph/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py index 5a6d4dda..9addf493 100644 --- a/pyqtgraph/opengl/GLViewWidget.py +++ b/pyqtgraph/opengl/GLViewWidget.py @@ -4,6 +4,7 @@ import OpenGL.GL.framebufferobjects as glfbo import numpy as np from .. import Vector from .. import functions as fn +from .. import getConfigOption import warnings from math import cos, sin, tan, radians ##Vector = QtGui.QVector3D @@ -90,7 +91,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): self.opts['azimuth'] = 45 ## camera's azimuthal angle in degrees ## (rotation around z-axis 0 points along x-axis) self.opts['viewport'] = None ## glViewport params; None == whole widget - self.setBackgroundColor('k') + self.setBackgroundColor(getConfigOption('background')) def addItem(self, item): self.items.append(item) From 1814ff535dada4c8df4fcc77b723facf292d1689 Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Sat, 17 Jul 2021 13:30:20 +0800 Subject: [PATCH 09/25] fail upfront for OpenGL ES instead of during item add --- pyqtgraph/opengl/GLViewWidget.py | 45 +++++++------------------------- 1 file changed, 9 insertions(+), 36 deletions(-) diff --git a/pyqtgraph/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py index 9addf493..c8cd36ab 100644 --- a/pyqtgraph/opengl/GLViewWidget.py +++ b/pyqtgraph/opengl/GLViewWidget.py @@ -97,14 +97,9 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): self.items.append(item) if self.isValid(): - self.makeCurrent() - try: - item.initialize() - except: - self.checkOpenGLVersion('Error while adding item %s to GLViewWidget.' % str(item)) + item.initialize() item._setView(self) - #print "set view", item, self, item.view() self.update() def removeItem(self, item): @@ -128,6 +123,14 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): """ Initialize items that were not initialized during addItem(). """ + ctx = self.context() + fmt = ctx.format() + if ctx.isOpenGLES() or fmt.version() < (2, 0): + verString = glGetString(GL_VERSION) + raise RuntimeError( + "pyqtgraph.opengl: Requires >= OpenGL 2.0 (not ES); Found %s" % verString + ) + for item in self.items: if not item.isInitialized(): item.initialize() @@ -523,36 +526,6 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): else: self.keyTimer.stop() - def checkOpenGLVersion(self, msg): - """ - Give exception additional context about version support. - - Only to be called from within exception handler. - As this check is only performed on error, - unsupported versions might still work! - """ - - # Check for unsupported version - verString = glGetString(GL_VERSION) - ver = verString.split()[0] - # If not OpenGL ES... - if str(ver.split(b'.')[0]).isdigit(): - verNumber = int(ver.split(b'.')[0]) - # ...and version is supported: - if verNumber >= 2: - # OpenGL version is fine, raise the original exception - raise - - # Print original exception - from .. import debug - debug.printExc() - - # Notify about unsupported version - raise Exception( - msg + "\n" + \ - "pyqtgraph.opengl: Requires >= OpenGL 2.0 (not ES); Found %s" % verString - ) - def readQImage(self): """ Read the current buffer pixels out as a QImage. From fab505a4314bdec94fd863b632ce7e94113e4ce2 Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Sat, 17 Jul 2021 13:32:33 +0800 Subject: [PATCH 10/25] remove unneeded call to makeCurrent() in any case, context will not be valid until the widget is shown. --- pyqtgraph/opengl/GLViewWidget.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyqtgraph/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py index c8cd36ab..bec221c1 100644 --- a/pyqtgraph/opengl/GLViewWidget.py +++ b/pyqtgraph/opengl/GLViewWidget.py @@ -54,8 +54,6 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): self.keysPressed = {} self.keyTimer = QtCore.QTimer() self.keyTimer.timeout.connect(self.evalKeyState) - self.makeCurrent() - def _updateScreen(self, screen): self._updatePixelRatio() From bc52a2afe08b3cc93f26bb28a133e56860907b16 Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Sat, 17 Jul 2021 13:46:11 +0800 Subject: [PATCH 11/25] return ValueError for wrong argument, not RuntimeError --- pyqtgraph/opengl/GLViewWidget.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyqtgraph/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py index bec221c1..f00e5446 100644 --- a/pyqtgraph/opengl/GLViewWidget.py +++ b/pyqtgraph/opengl/GLViewWidget.py @@ -33,8 +33,8 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): self.setFocusPolicy(QtCore.Qt.FocusPolicy.ClickFocus) - if rotationMethod not in {"euler", "quaternion"}: - raise RuntimeError("Rotation method should be either 'euler' or 'quaternion'") + if rotationMethod not in ["euler", "quaternion"]: + raise ValueError("Rotation method should be either 'euler' or 'quaternion'") self.opts = { 'center': Vector(0,0,0), ## will always appear at the center of the widget From 9bf6c01f5859b268217b20e35ba1f855e2a0179f Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Sat, 17 Jul 2021 14:41:15 +0800 Subject: [PATCH 12/25] fix renderToArray() broken for hidpi define opts['viewport'] to be in device pixels. note from the removed comments that there was one place assuming opts['viewport'] was in device pixels and the other assuming that it was in device independent pixels. --- pyqtgraph/opengl/GLViewWidget.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pyqtgraph/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py index f00e5446..1f080d37 100644 --- a/pyqtgraph/opengl/GLViewWidget.py +++ b/pyqtgraph/opengl/GLViewWidget.py @@ -45,6 +45,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): 'azimuth': 45, ## camera's azimuthal angle in degrees ## (rotation around z-axis 0 points along x-axis) 'viewport': None, ## glViewport params; None == whole widget + ## note that 'viewport' is in device pixels 'rotationMethod': rotationMethod } self.reset() @@ -146,10 +147,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): if vp is None: return (0, 0, self.deviceWidth(), self.deviceHeight()) else: - # note: the following code means that we have defined opts['viewport'] - # to be in device independent pixels. - dpr = self.devicePixelRatio() - return tuple([int(x * dpr) for x in vp]) + return vp def devicePixelRatio(self): return self.devicePixelRatioF() @@ -232,8 +230,6 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): if viewport is None: glViewport(*self.getViewport()) else: - # note: the following code means that we have defined "viewport" - # to be in device pixels. glViewport(*viewport) self.setProjection(region=region) self.setModelview() From f43f795950360e57bdff1c584b6ecd8c5a79d748 Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Sat, 17 Jul 2021 15:30:22 +0800 Subject: [PATCH 13/25] don't check opengl version again during paint we already fail upfront at initializeGL(), so any error that occurs here won't be due to OpenGL version < 2.0 --- pyqtgraph/opengl/GLViewWidget.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/pyqtgraph/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py index 1f080d37..5a2ec1b3 100644 --- a/pyqtgraph/opengl/GLViewWidget.py +++ b/pyqtgraph/opengl/GLViewWidget.py @@ -258,14 +258,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): except: from .. import debug debug.printExc() - msg = "Error while drawing item %s." % str(item) - ver = glGetString(GL_VERSION) - if ver is not None: - ver = ver.split()[0] - if int(ver.split(b'.')[0]) < 2: - print(msg + " The original exception is printed above; however, pyqtgraph requires OpenGL version 2.0 or greater for many of its 3D features and your OpenGL version is %s. Installing updated display drivers may resolve this issue." % ver) - else: - print(msg) + print("Error while drawing item %s." % str(item)) finally: glPopAttrib() From 5b2674c9d5ad6cf52d732fcab50a03aa81fb0b72 Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Sat, 17 Jul 2021 15:45:06 +0800 Subject: [PATCH 14/25] change some deviceWidth() to width() viewport / region use device pixels: deviceWidth() anywhere else uses device independent pixels : width() --- pyqtgraph/opengl/GLViewWidget.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyqtgraph/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py index 5a2ec1b3..7e89f489 100644 --- a/pyqtgraph/opengl/GLViewWidget.py +++ b/pyqtgraph/opengl/GLViewWidget.py @@ -382,7 +382,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): cVec = self.opts['center'] - cPos dist = cVec.length() ## distance from camera to center xDist = dist * 2. * tan(0.5 * radians(self.opts['fov'])) ## approx. width of view at distance of center point - xScale = xDist / self.deviceWidth() + xScale = xDist / self.width() zVec = QtGui.QVector3D(0,0,1) xVec = QtGui.QVector3D.crossProduct(zVec, cVec).normalized() yVec = QtGui.QVector3D.crossProduct(xVec, zVec).normalized() @@ -407,7 +407,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): fov = radians(self.opts['fov']) dist = (self.opts['center'] - self.cameraPosition()).length() fov_factor = tan(fov / 2) * 2 - scale_factor = dist * fov_factor / self.deviceWidth() + scale_factor = dist * fov_factor / self.width() z = scale_factor * cos(elev) * dy x = scale_factor * (sin(azim) * dx - sin(elev) * cos(azim) * dy) y = scale_factor * (cos(azim) * dx + sin(elev) * sin(azim) * dy) @@ -429,7 +429,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): else: dist = (pos-cam).length() xDist = dist * 2. * tan(0.5 * radians(self.opts['fov'])) - return xDist / self.deviceWidth() + return xDist / self.width() def mousePressEvent(self, ev): lpos = ev.position() if hasattr(ev, 'position') else ev.localPos() From dfd5b5dc1be95ab59222a60db0361f26dd4ab8a1 Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Sat, 17 Jul 2021 16:06:44 +0800 Subject: [PATCH 15/25] fix attribute access in failure branch --- pyqtgraph/opengl/GLViewWidget.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyqtgraph/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py index 7e89f489..f931fd72 100644 --- a/pyqtgraph/opengl/GLViewWidget.py +++ b/pyqtgraph/opengl/GLViewWidget.py @@ -540,8 +540,8 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): ## Test texture dimensions first glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA, texwidth, texwidth, 0, GL_RGBA, GL_UNSIGNED_BYTE, None) if glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH) == 0: - raise Exception("OpenGL failed to create 2D texture (%dx%d); too large for this hardware." % shape[:2]) - ## create teture + raise RuntimeError("OpenGL failed to create 2D texture (%dx%d); too large for this hardware." % data.shape[:2]) + ## create texture glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texwidth, texwidth, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.transpose((1,0,2))) # Create depth buffer From 7a17cda956f0789637a5885aa551d878e1d346f9 Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Sat, 17 Jul 2021 16:14:25 +0800 Subject: [PATCH 16/25] restore opts['viewport'] after clobbering --- pyqtgraph/opengl/GLViewWidget.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyqtgraph/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py index f931fd72..40f700d7 100644 --- a/pyqtgraph/opengl/GLViewWidget.py +++ b/pyqtgraph/opengl/GLViewWidget.py @@ -550,6 +550,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, texwidth, texwidth) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth_buf) + viewport_orig = self.opts['viewport'] self.opts['viewport'] = (0, 0, w, h) # viewport is the complete image; this ensures that paintGL(region=...) # is interpreted correctly. p2 = 2 * padding @@ -572,7 +573,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): output[x+padding:x2-padding, y+padding:y2-padding] = data[padding:w2-padding, -(h2-padding):-padding] finally: - self.opts['viewport'] = None + self.opts['viewport'] = viewport_orig glfbo.glBindFramebuffer(glfbo.GL_FRAMEBUFFER, 0) glBindTexture(GL_TEXTURE_2D, 0) if tex is not None: From 6ca81fdddb5a2de8542b56089d7a9f99d6c06998 Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Sat, 17 Jul 2021 18:25:06 +0800 Subject: [PATCH 17/25] Revert "restore opts['viewport'] after clobbering" This reverts commit 7a17cda956f0789637a5885aa551d878e1d346f9. --- pyqtgraph/opengl/GLViewWidget.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyqtgraph/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py index 40f700d7..f931fd72 100644 --- a/pyqtgraph/opengl/GLViewWidget.py +++ b/pyqtgraph/opengl/GLViewWidget.py @@ -550,7 +550,6 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, texwidth, texwidth) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth_buf) - viewport_orig = self.opts['viewport'] self.opts['viewport'] = (0, 0, w, h) # viewport is the complete image; this ensures that paintGL(region=...) # is interpreted correctly. p2 = 2 * padding @@ -573,7 +572,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): output[x+padding:x2-padding, y+padding:y2-padding] = data[padding:w2-padding, -(h2-padding):-padding] finally: - self.opts['viewport'] = viewport_orig + self.opts['viewport'] = None glfbo.glBindFramebuffer(glfbo.GL_FRAMEBUFFER, 0) glBindTexture(GL_TEXTURE_2D, 0) if tex is not None: From e10dbfd9e1a75640e8e3d5315339dd6175189b52 Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Sat, 17 Jul 2021 17:37:38 +0800 Subject: [PATCH 18/25] change examples to use setCameraPosition --- examples/GLBarGraphItem.py | 2 +- examples/GLImageItem.py | 2 +- examples/GLLinePlotItem.py | 2 +- examples/GLScatterPlotItem.py | 2 +- examples/GLViewWidget.py | 2 +- examples/GLVolumeItem.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/GLBarGraphItem.py b/examples/GLBarGraphItem.py index 5d1cbcb3..094354a2 100644 --- a/examples/GLBarGraphItem.py +++ b/examples/GLBarGraphItem.py @@ -13,9 +13,9 @@ import numpy as np app = pg.mkQApp("GLBarGraphItem Example") w = gl.GLViewWidget() -w.opts['distance'] = 40 w.show() w.setWindowTitle('pyqtgraph example: GLBarGraphItem') +w.setCameraPosition(distance=40) gx = gl.GLGridItem() gx.rotate(90, 0, 1, 0) diff --git a/examples/GLImageItem.py b/examples/GLImageItem.py index 8648391c..1fc30acf 100644 --- a/examples/GLImageItem.py +++ b/examples/GLImageItem.py @@ -15,9 +15,9 @@ import numpy as np app = pg.mkQApp("GLImageItem Example") w = gl.GLViewWidget() -w.opts['distance'] = 200 w.show() w.setWindowTitle('pyqtgraph example: GLImageItem') +w.setCameraPosition(distance=200) ## create volume data set to slice three images from shape = (100,100,70) diff --git a/examples/GLLinePlotItem.py b/examples/GLLinePlotItem.py index c77034fc..9e60921e 100644 --- a/examples/GLLinePlotItem.py +++ b/examples/GLLinePlotItem.py @@ -13,9 +13,9 @@ import numpy as np app = pg.mkQApp("GLLinePlotItem Example") w = gl.GLViewWidget() -w.opts['distance'] = 40 w.show() w.setWindowTitle('pyqtgraph example: GLLinePlotItem') +w.setCameraPosition(distance=40) gx = gl.GLGridItem() gx.rotate(90, 0, 1, 0) diff --git a/examples/GLScatterPlotItem.py b/examples/GLScatterPlotItem.py index f07e0091..5326952d 100644 --- a/examples/GLScatterPlotItem.py +++ b/examples/GLScatterPlotItem.py @@ -15,9 +15,9 @@ import numpy as np app = pg.mkQApp("GLScatterPlotItem Example") w = gl.GLViewWidget() -w.opts['distance'] = 20 w.show() w.setWindowTitle('pyqtgraph example: GLScatterPlotItem') +w.setCameraPosition(distance=20) g = gl.GLGridItem() w.addItem(g) diff --git a/examples/GLViewWidget.py b/examples/GLViewWidget.py index 910fa8eb..234f605e 100644 --- a/examples/GLViewWidget.py +++ b/examples/GLViewWidget.py @@ -11,9 +11,9 @@ import pyqtgraph.opengl as gl pg.mkQApp("GLViewWidget Example") w = gl.GLViewWidget() -w.opts['distance'] = 20 w.show() w.setWindowTitle('pyqtgraph example: GLViewWidget') +w.setCameraPosition(distance=20) ax = gl.GLAxisItem() ax.setSize(5,5,5) diff --git a/examples/GLVolumeItem.py b/examples/GLVolumeItem.py index ca790fd3..b49c0b34 100644 --- a/examples/GLVolumeItem.py +++ b/examples/GLVolumeItem.py @@ -14,9 +14,9 @@ from pyqtgraph import functions as fn app = pg.mkQApp("GLVolumeItem Example") w = gl.GLViewWidget() -w.opts['distance'] = 200 w.show() w.setWindowTitle('pyqtgraph example: GLVolumeItem') +w.setCameraPosition(distance=200) g = gl.GLGridItem() g.scale(10, 10, 1) From f85a1015ad3bda9817a93c7d620e99e1415ae7be Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Sat, 17 Jul 2021 18:28:09 +0800 Subject: [PATCH 19/25] fix GLTextItem to use relative imports --- pyqtgraph/opengl/items/GLTextItem.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyqtgraph/opengl/items/GLTextItem.py b/pyqtgraph/opengl/items/GLTextItem.py index 59a4741d..b1b1b6c0 100644 --- a/pyqtgraph/opengl/items/GLTextItem.py +++ b/pyqtgraph/opengl/items/GLTextItem.py @@ -1,8 +1,8 @@ from OpenGL.GL import * import numpy as np -from pyqtgraph.Qt import QtCore, QtGui -from pyqtgraph.opengl.GLGraphicsItem import GLGraphicsItem -import pyqtgraph.functions as fn +from ...Qt import QtCore, QtGui +from .. GLGraphicsItem import GLGraphicsItem +from ... import functions as fn __all__ = ['GLTextItem'] From 31e10fdc1d62169134e61cc1ed576f10f2aded6b Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Sat, 17 Jul 2021 18:29:42 +0800 Subject: [PATCH 20/25] raise ValueError instead of ctypes.ArgumentError ctypes.ArgumentError got imported through PyOpenGL import * --- pyqtgraph/opengl/items/GLTextItem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyqtgraph/opengl/items/GLTextItem.py b/pyqtgraph/opengl/items/GLTextItem.py index b1b1b6c0..1a79095a 100644 --- a/pyqtgraph/opengl/items/GLTextItem.py +++ b/pyqtgraph/opengl/items/GLTextItem.py @@ -39,7 +39,7 @@ class GLTextItem(GLGraphicsItem): args = ['pos', 'color', 'text', 'font'] for k in kwds.keys(): if k not in args: - raise ArgumentError('Invalid keyword argument: %s (allowed arguments are %s)' % (k, str(args))) + raise ValueError('Invalid keyword argument: %s (allowed arguments are %s)' % (k, str(args))) for arg in args: if arg in kwds: value = kwds[arg] From aca627ac8c71fac166c46f6d5451fc9fe09618d1 Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Sat, 17 Jul 2021 18:39:59 +0800 Subject: [PATCH 21/25] GLTextItem: use device independent pixels for viewport --- pyqtgraph/opengl/items/GLTextItem.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyqtgraph/opengl/items/GLTextItem.py b/pyqtgraph/opengl/items/GLTextItem.py index 1a79095a..49e4c94e 100644 --- a/pyqtgraph/opengl/items/GLTextItem.py +++ b/pyqtgraph/opengl/items/GLTextItem.py @@ -65,12 +65,11 @@ class GLTextItem(GLGraphicsItem): modelview = glGetDoublev(GL_MODELVIEW_MATRIX) projection = glGetDoublev(GL_PROJECTION_MATRIX) - viewport = glGetIntegerv(GL_VIEWPORT) + viewport = [0, 0, self.view().width(), self.view().height()] text_pos = self.__project(self.pos, modelview, projection, viewport) text_pos.setY(viewport[3] - text_pos.y()) - text_pos /= self.view().devicePixelRatio() painter = QtGui.QPainter(self.view()) painter.setPen(self.color) From 5283eeb71b34c175d5c63f0aa384496e9a66da34 Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Sat, 17 Jul 2021 18:55:55 +0800 Subject: [PATCH 22/25] remove override of devicePixelRatio() --- pyqtgraph/opengl/GLViewWidget.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/pyqtgraph/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py index f931fd72..f65edec6 100644 --- a/pyqtgraph/opengl/GLViewWidget.py +++ b/pyqtgraph/opengl/GLViewWidget.py @@ -72,11 +72,11 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): self._updateScreen(window.screen()) def deviceWidth(self): - dpr = self.devicePixelRatio() + dpr = self.devicePixelRatioF() return int(self.width() * dpr) def deviceHeight(self): - dpr = self.devicePixelRatio() + dpr = self.devicePixelRatioF() return int(self.height() * dpr) def reset(self): @@ -149,9 +149,6 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): else: return vp - def devicePixelRatio(self): - return self.devicePixelRatioF() - def setProjection(self, region=None): m = self.projectionMatrix(region) glMatrixMode(GL_PROJECTION) From fa77dae9416fb0d3e801e15cec50f01f4fd10df6 Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Sat, 17 Jul 2021 15:19:11 +0800 Subject: [PATCH 23/25] render upright image (was previously transposed image) --- pyqtgraph/opengl/GLViewWidget.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyqtgraph/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py index f65edec6..64adb79c 100644 --- a/pyqtgraph/opengl/GLViewWidget.py +++ b/pyqtgraph/opengl/GLViewWidget.py @@ -524,7 +524,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): fb = None depth_buf = None try: - output = np.empty((w, h, 4), dtype=np.ubyte) + output = np.empty((h, w, 4), dtype=np.ubyte) fb = glfbo.glGenFramebuffers(1) glfbo.glBindFramebuffer(glfbo.GL_FRAMEBUFFER, fb ) @@ -539,7 +539,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): if glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH) == 0: raise RuntimeError("OpenGL failed to create 2D texture (%dx%d); too large for this hardware." % data.shape[:2]) ## create texture - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texwidth, texwidth, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.transpose((1,0,2))) + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texwidth, texwidth, 0, GL_RGBA, GL_UNSIGNED_BYTE, data) # Create depth buffer depth_buf = glGenRenderbuffers(1) @@ -565,8 +565,8 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): ## read texture back to array data = glGetTexImage(GL_TEXTURE_2D, 0, format, type) - data = np.fromstring(data, dtype=np.ubyte).reshape(texwidth,texwidth,4).transpose(1,0,2)[:, ::-1] - output[x+padding:x2-padding, y+padding:y2-padding] = data[padding:w2-padding, -(h2-padding):-padding] + data = np.frombuffer(data, dtype=np.ubyte).reshape(texwidth,texwidth,4)[::-1, ...] + output[y+padding:y2-padding, x+padding:x2-padding] = data[-(h2-padding):-padding, padding:w2-padding] finally: self.opts['viewport'] = None From 6f49ede5c1794ead6ab085644495ee42071f37d7 Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Sun, 18 Jul 2021 09:09:57 +0800 Subject: [PATCH 24/25] glDisable(GL_TEXTURE_3D) -> glDisable(GL_TEXTURE_2D) --- pyqtgraph/opengl/items/GLImageItem.py | 2 +- pyqtgraph/widgets/RawImageWidget.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyqtgraph/opengl/items/GLImageItem.py b/pyqtgraph/opengl/items/GLImageItem.py index 7bd0ec02..dabe1c41 100644 --- a/pyqtgraph/opengl/items/GLImageItem.py +++ b/pyqtgraph/opengl/items/GLImageItem.py @@ -99,5 +99,5 @@ class GLImageItem(GLGraphicsItem): glTexCoord2f(0,1) glVertex3f(0, self.data.shape[1], 0) glEnd() - glDisable(GL_TEXTURE_3D) + glDisable(GL_TEXTURE_2D) diff --git a/pyqtgraph/widgets/RawImageWidget.py b/pyqtgraph/widgets/RawImageWidget.py index 2a12127f..f36c5cd0 100644 --- a/pyqtgraph/widgets/RawImageWidget.py +++ b/pyqtgraph/widgets/RawImageWidget.py @@ -162,4 +162,4 @@ if HAVE_OPENGL: glTexCoord2f(0, 0) glVertex3f(-1, 1, 0) glEnd() - glDisable(GL_TEXTURE_3D) + glDisable(GL_TEXTURE_2D) From cbc9b4d310448dc4e9f89c4288b294b113312883 Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Sun, 18 Jul 2021 10:45:58 +0800 Subject: [PATCH 25/25] catch specific KeyError exception --- pyqtgraph/opengl/GLViewWidget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyqtgraph/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py index 64adb79c..723b288b 100644 --- a/pyqtgraph/opengl/GLViewWidget.py +++ b/pyqtgraph/opengl/GLViewWidget.py @@ -486,7 +486,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): return try: del self.keysPressed[ev.key()] - except: + except KeyError: self.keysPressed = {} self.evalKeyState()