diff --git a/examples/GLBarGraphItem.py b/examples/GLBarGraphItem.py index 094354a2..5293a795 100644 --- a/examples/GLBarGraphItem.py +++ b/examples/GLBarGraphItem.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ -Demonstrate use of GLLinePlotItem to draw cross-sections of a surface. +This example demonstrates the use of GLBarGraphItem. """ ## Add path to library (just for examples; you do not need this) diff --git a/examples/GLPainterItem.py b/examples/GLPainterItem.py new file mode 100644 index 00000000..ae54b66a --- /dev/null +++ b/examples/GLPainterItem.py @@ -0,0 +1,94 @@ +""" +Demonstrate using QPainter on a subclass of GLGraphicsItem. +""" +## Add path to library (just for examples; you do not need this) +import initExample + +import pyqtgraph as pg +import pyqtgraph.opengl +from pyqtgraph.Qt import QtCore, QtGui +import OpenGL.GL as GL + +SIZE = 32 + +class GLPainterItem(pg.opengl.GLGraphicsItem.GLGraphicsItem): + def __init__(self, **kwds): + super().__init__() + glopts = kwds.pop('glOptions', 'additive') + self.setGLOptions(glopts) + + def compute_projection(self): + modelview = GL.glGetDoublev(GL.GL_MODELVIEW_MATRIX) + projection = GL.glGetDoublev(GL.GL_PROJECTION_MATRIX) + mvp = projection.T @ modelview.T + mvp = QtGui.QMatrix4x4(mvp.ravel().tolist()) + + # note that QRectF.bottom() != QRect.bottom() + rect = QtCore.QRectF(self.view().rect()) + ndc_to_viewport = QtGui.QMatrix4x4() + ndc_to_viewport.viewport(rect.left(), rect.bottom(), rect.width(), -rect.height()) + + return ndc_to_viewport * mvp + + def paint(self): + self.setupGLState() + + painter = QtGui.QPainter(self.view()) + self.draw(painter) + painter.end() + + def draw(self, painter): + painter.setPen(QtCore.Qt.GlobalColor.white) + painter.setRenderHints(QtGui.QPainter.RenderHint.Antialiasing | QtGui.QPainter.RenderHint.TextAntialiasing) + + rect = self.view().rect() + af = QtCore.Qt.AlignmentFlag + + painter.drawText(rect, af.AlignTop | af.AlignRight, 'TR') + painter.drawText(rect, af.AlignBottom | af.AlignLeft, 'BL') + painter.drawText(rect, af.AlignBottom | af.AlignRight, 'BR') + + opts = self.view().cameraParams() + lines = [] + center = opts['center'] + lines.append(f"center : ({center.x():.1f}, {center.y():.1f}, {center.z():.1f})") + for key in ['distance', 'fov', 'elevation', 'azimuth']: + lines.append(f"{key} : {opts[key]:.1f}") + xyz = self.view().cameraPosition() + lines.append(f"xyz : ({xyz.x():.1f}, {xyz.y():.1f}, {xyz.z():.1f})") + info = "\n".join(lines) + painter.drawText(rect, af.AlignTop | af.AlignLeft, info) + + project = self.compute_projection() + + hsize = SIZE // 2 + for xi in range(-hsize, hsize+1): + for yi in range(-hsize, hsize+1): + if xi == -hsize and yi == -hsize: + # skip one corner for visual orientation + continue + vec3 = QtGui.QVector3D(xi, yi, 0) + pos = project.map(vec3).toPointF() + painter.drawEllipse(pos, 1, 1) + + +pg.mkQApp("GLPainterItem Example") +glv = pg.opengl.GLViewWidget() +glv.show() +glv.setWindowTitle('pyqtgraph example: GLPainterItem') +glv.setCameraPosition(distance=50, elevation=90, azimuth=0) + +griditem = pg.opengl.GLGridItem() +griditem.setSize(SIZE, SIZE) +griditem.setSpacing(1, 1) +glv.addItem(griditem) + +axisitem = pg.opengl.GLAxisItem() +axisitem.setSize(SIZE/2, SIZE/2, 1) +glv.addItem(axisitem) + +paintitem = GLPainterItem() +glv.addItem(paintitem) + +if __name__ == '__main__': + pg.exec() diff --git a/examples/test_examples.py b/examples/test_examples.py index 473a161a..ae5cf8de 100644 --- a/examples/test_examples.py +++ b/examples/test_examples.py @@ -70,51 +70,15 @@ conditionalExamples = { False, reason="Test is being problematic on CI machines" ), - 'GLVolumeItem.py': exceptionCondition( - not darwin_opengl_broken, - reason=darwin_opengl_reason - ), - 'GLIsosurface.py': exceptionCondition( - not darwin_opengl_broken, - reason=darwin_opengl_reason - ), - 'GLSurfacePlot.py': exceptionCondition( - not darwin_opengl_broken, - reason=darwin_opengl_reason - ), - 'GLScatterPlotItem.py': exceptionCondition( - not darwin_opengl_broken, - reason=darwin_opengl_reason - ), - 'GLshaders.py': exceptionCondition( - not darwin_opengl_broken, - reason=darwin_opengl_reason - ), - 'GLTextItem.py': exceptionCondition( - not darwin_opengl_broken, - reason=darwin_opengl_reason - ), - 'GLLinePlotItem.py': exceptionCondition( - not darwin_opengl_broken, - reason=darwin_opengl_reason - ), - 'GLMeshItem.py': exceptionCondition( - not darwin_opengl_broken, - reason=darwin_opengl_reason - ), - 'GLImageItem.py': exceptionCondition( - not darwin_opengl_broken, - reason=darwin_opengl_reason - ), - 'GLBarGraphItem.py': exceptionCondition( - not darwin_opengl_broken, - reason=darwin_opengl_reason - ), - 'GLViewWidget.py': exceptionCondition( +} + +openglExamples = ['GLViewWidget.py'] +openglExamples.extend(utils.examples_['3D Graphics'].values()) +for key in openglExamples: + conditionalExamples[key] = exceptionCondition( not darwin_opengl_broken, reason=darwin_opengl_reason ) -} @pytest.mark.skipif( Qt.QT_LIB == "PySide2" diff --git a/examples/utils.py b/examples/utils.py index 8f8ff609..911f55c7 100644 --- a/examples/utils.py +++ b/examples/utils.py @@ -74,6 +74,8 @@ examples_ = OrderedDict([ ('Mesh', 'GLMeshItem.py'), ('Image', 'GLImageItem.py'), ('Text', 'GLTextItem.py'), + ('BarGraph', 'GLBarGraphItem.py'), + ('Painter', 'GLPainterItem.py'), ])), ('Widgets', OrderedDict([ ('PlotWidget', 'PlotWidget.py'), @@ -105,7 +107,6 @@ others = dict([ ('ScaleBar', 'ScaleBar.py'), ('ViewBox', 'ViewBox.py'), ('GradientEditor', 'GradientEditor.py'), - ('GLBarGraphItem', 'GLBarGraphItem.py'), ('GLViewWidget', 'GLViewWidget.py'), ('DiffTreeWidget', 'DiffTreeWidget.py'), ('MultiPlotWidget', 'MultiPlotWidget.py'), diff --git a/pyqtgraph/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py index 275599fc..f311c776 100644 --- a/pyqtgraph/opengl/GLViewWidget.py +++ b/pyqtgraph/opengl/GLViewWidget.py @@ -326,6 +326,21 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): ) return pos + def setCameraParams(self, **kwds): + valid_keys = {'center', 'rotation', 'distance', 'fov', 'elevation', 'azimuth'} + if not valid_keys.issuperset(kwds): + raise ValueError(f'valid keywords are {valid_keys}') + + self.setCameraPosition(pos=kwds.get('center'), distance=kwds.get('distance'), + elevation=kwds.get('elevation'), azimuth=kwds.get('azimuth'), + rotation=kwds.get('rotation')) + if 'fov' in kwds: + self.opts['fov'] = kwds['fov'] + + def cameraParams(self): + valid_keys = {'center', 'rotation', 'distance', 'fov', 'elevation', 'azimuth'} + return { k : self.opts[k] for k in valid_keys } + def orbit(self, azim, elev): """Orbits the camera around the center position. *azim* and *elev* are given in degrees.""" if self.opts['rotationMethod'] == 'quaternion':