2012-03-06 06:22:02 +00:00
from pyqtgraph . Qt import QtCore , QtGui , QtOpenGL
from OpenGL . GL import *
import numpy as np
Vector = QtGui . QVector3D
class GLViewWidget ( QtOpenGL . QGLWidget ) :
"""
Basic widget for displaying 3 D data
- Rotation / scale controls
- Axis / grid display
- Export options
"""
def __init__ ( self , parent = None ) :
QtOpenGL . QGLWidget . __init__ ( self , parent )
2012-04-28 19:12:46 +00:00
self . setFocusPolicy ( QtCore . Qt . ClickFocus )
2012-03-06 06:22:02 +00:00
self . opts = {
' center ' : Vector ( 0 , 0 , 0 ) , ## will always appear at the center of the widget
' distance ' : 10.0 , ## distance of camera from center
' fov ' : 60 , ## horizontal field of view in degrees
' elevation ' : 30 , ## camera's angle of elevation in degrees
' azimuth ' : 45 , ## camera's azimuthal angle in degrees
## (rotation around z-axis 0 points along x-axis)
}
self . items = [ ]
2012-04-28 19:12:46 +00:00
self . noRepeatKeys = [ QtCore . Qt . Key_Right , QtCore . Qt . Key_Left , QtCore . Qt . Key_Up , QtCore . Qt . Key_Down , QtCore . Qt . Key_PageUp , QtCore . Qt . Key_PageDown ]
self . keysPressed = { }
self . keyTimer = QtCore . QTimer ( )
self . keyTimer . timeout . connect ( self . evalKeyState )
2012-07-09 01:33:35 +00:00
self . makeCurrent ( )
2012-03-06 06:22:02 +00:00
def addItem ( self , item ) :
self . items . append ( item )
if hasattr ( item , ' initializeGL ' ) :
self . makeCurrent ( )
2012-07-09 01:33:35 +00:00
try :
item . initializeGL ( )
except :
self . checkOpenGLVersion ( ' Error while adding item %s to GLViewWidget. ' % str ( item ) )
2012-03-06 06:22:02 +00:00
item . _setView ( self )
#print "set view", item, self, item.view()
2012-04-28 19:12:46 +00:00
self . update ( )
2012-03-06 06:22:02 +00:00
2012-03-09 17:38:15 +00:00
def removeItem ( self , item ) :
self . items . remove ( item )
item . _setView ( None )
2012-04-28 19:12:46 +00:00
self . update ( )
2012-03-09 17:38:15 +00:00
2012-03-06 06:22:02 +00:00
def initializeGL ( self ) :
glClearColor ( 0.0 , 0.0 , 0.0 , 0.0 )
self . resizeGL ( self . width ( ) , self . height ( ) )
def resizeGL ( self , w , h ) :
glViewport ( 0 , 0 , w , h )
2012-04-28 19:12:46 +00:00
#self.update()
2012-03-06 06:22:02 +00:00
def setProjection ( self ) :
## Create the projection matrix
glMatrixMode ( GL_PROJECTION )
glLoadIdentity ( )
w = self . width ( )
h = self . height ( )
dist = self . opts [ ' distance ' ]
fov = self . opts [ ' fov ' ]
nearClip = dist * 0.001
farClip = dist * 1000.
2012-04-28 19:12:46 +00:00
r = nearClip * np . tan ( fov * 0.5 * np . pi / 180. )
2012-03-06 06:22:02 +00:00
t = r * h / w
glFrustum ( - r , r , - t , t , nearClip , farClip )
def setModelview ( self ) :
glMatrixMode ( GL_MODELVIEW )
glLoadIdentity ( )
glTranslatef ( 0.0 , 0.0 , - self . opts [ ' distance ' ] )
glRotatef ( self . opts [ ' elevation ' ] - 90 , 1 , 0 , 0 )
glRotatef ( self . opts [ ' azimuth ' ] + 90 , 0 , 0 , - 1 )
center = self . opts [ ' center ' ]
2012-04-28 19:12:46 +00:00
glTranslatef ( - center . x ( ) , - center . y ( ) , - center . z ( ) )
2012-03-06 06:22:02 +00:00
def paintGL ( self ) :
self . setProjection ( )
self . setModelview ( )
glClear ( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT )
self . drawItemTree ( )
def drawItemTree ( self , item = None ) :
if item is None :
items = [ x for x in self . items if x . parentItem ( ) is None ]
else :
items = item . childItems ( )
items . append ( item )
items . sort ( lambda a , b : cmp ( a . depthValue ( ) , b . depthValue ( ) ) )
for i in items :
2012-03-09 17:38:15 +00:00
if not i . visible ( ) :
continue
2012-03-06 06:22:02 +00:00
if i is item :
2012-04-28 19:12:46 +00:00
try :
glPushAttrib ( GL_ALL_ATTRIB_BITS )
i . paint ( )
except :
2012-07-09 01:33:35 +00:00
import pyqtgraph . debug
pyqtgraph . debug . printExc ( )
msg = " Error while drawing item %s . " % str ( item )
ver = glGetString ( GL_VERSION ) . split ( ) [ 0 ]
if int ( ver . split ( ' . ' ) [ 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 )
2012-04-28 19:12:46 +00:00
finally :
glPopAttrib ( GL_ALL_ATTRIB_BITS )
2012-03-09 17:38:15 +00:00
else :
2012-03-06 06:22:02 +00:00
glMatrixMode ( GL_MODELVIEW )
glPushMatrix ( )
2012-03-09 17:38:15 +00:00
tr = i . transform ( )
a = np . array ( tr . copyDataTo ( ) ) . reshape ( ( 4 , 4 ) )
glMultMatrixf ( a . transpose ( ) )
self . drawItemTree ( i )
2012-03-06 06:22:02 +00:00
glMatrixMode ( GL_MODELVIEW )
glPopMatrix ( )
def cameraPosition ( self ) :
""" Return current position of camera based on center, dist, elevation, and azimuth """
center = self . opts [ ' center ' ]
dist = self . opts [ ' distance ' ]
elev = self . opts [ ' elevation ' ] * np . pi / 180.
azim = self . opts [ ' azimuth ' ] * np . pi / 180.
pos = Vector (
center . x ( ) + dist * np . cos ( elev ) * np . cos ( azim ) ,
center . y ( ) + dist * np . cos ( elev ) * np . sin ( azim ) ,
center . z ( ) + dist * np . sin ( elev )
)
return pos
2012-04-28 19:12:46 +00:00
def orbit ( self , azim , elev ) :
""" Orbits the camera around the center position. *azim* and *elev* are given in degrees. """
self . opts [ ' azimuth ' ] + = azim
self . opts [ ' elevation ' ] = np . clip ( self . opts [ ' elevation ' ] + elev , - 90 , 90 )
self . update ( )
def pan ( self , dx , dy , dz , relative = False ) :
"""
Moves the center ( look - at ) position while holding the camera in place .
If relative = True , then the coordinates are interpreted such that x
if in the global xy plane and points to the right side of the view , y is
in the global xy plane and orthogonal to x , and z points in the global z
direction . Distances are scaled roughly such that a value of 1.0 moves
by one pixel on screen .
"""
if not relative :
self . opts [ ' center ' ] + = QtGui . QVector3D ( dx , dy , dz )
else :
cPos = self . cameraPosition ( )
cVec = self . opts [ ' center ' ] - cPos
dist = cVec . length ( ) ## distance from camera to center
xDist = dist * 2. * np . tan ( 0.5 * self . opts [ ' fov ' ] * np . pi / 180. ) ## approx. width of view at distance of center point
xScale = xDist / self . width ( )
zVec = QtGui . QVector3D ( 0 , 0 , 1 )
xVec = QtGui . QVector3D . crossProduct ( zVec , cVec ) . normalized ( )
yVec = QtGui . QVector3D . crossProduct ( xVec , zVec ) . normalized ( )
self . opts [ ' center ' ] = self . opts [ ' center ' ] + xVec * xScale * dx + yVec * xScale * dy + zVec * xScale * dz
self . update ( )
def pixelSize ( self , pos ) :
"""
Return the approximate size of a screen pixel at the location pos
"""
cam = self . cameraPosition ( )
dist = ( pos - cam ) . length ( )
xDist = dist * 2. * np . tan ( 0.5 * self . opts [ ' fov ' ] * np . pi / 180. )
return xDist / self . width ( )
2012-03-06 06:22:02 +00:00
def mousePressEvent ( self , ev ) :
self . mousePos = ev . pos ( )
def mouseMoveEvent ( self , ev ) :
diff = ev . pos ( ) - self . mousePos
self . mousePos = ev . pos ( )
2012-04-28 19:12:46 +00:00
if ev . buttons ( ) == QtCore . Qt . LeftButton :
self . orbit ( - diff . x ( ) , diff . y ( ) )
#print self.opts['azimuth'], self.opts['elevation']
elif ev . buttons ( ) == QtCore . Qt . MidButton :
if ( ev . modifiers ( ) & QtCore . Qt . ControlModifier ) :
self . pan ( diff . x ( ) , 0 , diff . y ( ) , relative = True )
else :
self . pan ( diff . x ( ) , diff . y ( ) , 0 , relative = True )
2012-03-06 06:22:02 +00:00
def mouseReleaseEvent ( self , ev ) :
pass
def wheelEvent ( self , ev ) :
2012-04-28 19:12:46 +00:00
if ( ev . modifiers ( ) & QtCore . Qt . ControlModifier ) :
self . opts [ ' fov ' ] * = 0.999 * * ev . delta ( )
else :
self . opts [ ' distance ' ] * = 0.999 * * ev . delta ( )
self . update ( )
2012-03-06 06:22:02 +00:00
2012-04-28 19:12:46 +00:00
def keyPressEvent ( self , ev ) :
if ev . key ( ) in self . noRepeatKeys :
ev . accept ( )
if ev . isAutoRepeat ( ) :
return
self . keysPressed [ ev . key ( ) ] = 1
self . evalKeyState ( )
def keyReleaseEvent ( self , ev ) :
if ev . key ( ) in self . noRepeatKeys :
ev . accept ( )
if ev . isAutoRepeat ( ) :
return
try :
del self . keysPressed [ ev . key ( ) ]
except :
self . keysPressed = { }
self . evalKeyState ( )
def evalKeyState ( self ) :
speed = 2.0
if len ( self . keysPressed ) > 0 :
for key in self . keysPressed :
if key == QtCore . Qt . Key_Right :
self . orbit ( azim = - speed , elev = 0 )
elif key == QtCore . Qt . Key_Left :
self . orbit ( azim = speed , elev = 0 )
elif key == QtCore . Qt . Key_Up :
self . orbit ( azim = 0 , elev = - speed )
elif key == QtCore . Qt . Key_Down :
self . orbit ( azim = 0 , elev = speed )
elif key == QtCore . Qt . Key_PageUp :
pass
elif key == QtCore . Qt . Key_PageDown :
pass
self . keyTimer . start ( 16 )
else :
self . keyTimer . stop ( )
2012-03-06 06:22:02 +00:00
2012-07-09 01:33:35 +00:00
def checkOpenGLVersion ( self , msg ) :
## Only to be called from within exception handler.
ver = glGetString ( GL_VERSION ) . split ( ) [ 0 ]
if int ( ver . split ( ' . ' ) [ 0 ] ) < 2 :
import pyqtgraph . debug
pyqtgraph . debug . printExc ( )
raise Exception ( 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 :
raise
2012-04-28 19:12:46 +00:00