from OpenGL.GL import *
try:
from PyQt5 import QtGui
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtOpenGL import *
except ImportError:
from PyQt4 import QtGui
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtOpenGL import *
import sys
import math
import weakref
import signal
GLUT_UP = 1
GLUT_DOWN = 0
GLUT_ACTIVE_CTRL = 2
GLUT_ACTIVE_SHIFT = 1
GLUT_ACTIVE_ALT = 4
keymap = {Qt.Key_F1:'f1',
Qt.Key_F2:'f2',
Qt.Key_F3:'f3',
Qt.Key_F4:'f4',
Qt.Key_F5:'f5',
Qt.Key_F6:'f6',
Qt.Key_F7:'f7',
Qt.Key_F8:'f8',
Qt.Key_F9:'f9',
Qt.Key_F10:'f10',
Qt.Key_F11:'f11',
Qt.Key_F12:'f12',
Qt.Key_Up:'up',
Qt.Key_Left:'left',
Qt.Key_Down:'down',
Qt.Key_Right:'right',
Qt.Key_Home:'home',
Qt.Key_End:'end',
Qt.Key_Delete:'delete',
Qt.Key_Enter:'enter'
}
[docs]def toModifierList(modifiers):
res = []
if modifiers & Qt.AltModifier:
res.append('alt')
if modifiers & Qt.ShiftModifier:
res.append('shift')
if modifiers & Qt.ControlModifier:
res.append('ctrl')
return res
[docs]class QtGLWindow(QGLWidget):
"""A basic OpenGL window using Qt. Should not be used directly, use
the functions in QtBackend instead.
Attributes:
name (str): title of the window (only has an effect before calling run())
width, height (int): width/height of the window in screen units. These are initialized
from the GLProgram viewport on run(), but thereafter Qt manages them. After, the
user must call resize(w,h) to change the dimensions.
devwidth, devheight (int): width/height of the window in OpenGL device pixel units
(Note that these may be different from the screen dimensions due to Retina displays)
"""
idlesleep_signal = pyqtSignal(float)
refresh_signal = pyqtSignal()
reshape_signal = pyqtSignal(int,int)
def __init__(self,name="OpenGL window",parent=None):
format = QGLFormat()
if not format.hasOpenGL():
raise RuntimeError("It appears that your system doesn't have OpenGL support?")
format.setRgba(True)
format.setDoubleBuffer(True)
format.setDepth(True)
format.setSampleBuffers(True)
format.setSamples(4)
if not hasattr(QtGLWindow,"_firstWidget"):
QtGLWindow._firstWidget = self
QGLWidget.__init__(self,format,parent)
else:
shareWidget = QtGLWindow._firstWidget
QGLWidget.__init__(self,format,shareWidget=shareWidget)
#self.setContext(self.context(),shareContext=shareWidget.context())
if not self.isValid():
raise RuntimeError("Unspecified error creating the Qt GLWidget, OpenGL rendering is not supported")
self.name = name
self.program = None
self.width = 640
self.height = 480
if hasattr(self,'devicePixelRatio'):
self.devheight = self.devicePixelRatio()*self.height
self.devwidth = self.devicePixelRatio()*self.width
else:
self.devheight = self.height
self.devwidth = self.width
self.sizePolicy = "resize"
#keyboard state information
self.modifierList = []
#mouse state information
self.lastx,self.lasty = None,None
self.initialized = False
self.refreshed = False
self.setFixedSize(self.width,self.height)
self.setWindowTitle(self.name)
self.idleTimer = None
self.nextIdleEvent = 0
self.actions = []
self.actionMenu = None
self.inpaint = False
self.idlesleep_signal.connect(self.do_idlesleep)
self.refresh_signal.connect(self.do_refresh)
self.reshape_signal.connect(self.do_reshape)
[docs] def setProgram(self,program):
"""User will call this to set up the program variable"""
from ..glprogram import GLProgram
assert isinstance(program,GLProgram)
print("######### QGLWidget setProgram ###############")
if hasattr(program,'name'):
self.name = program.name
if self.initialized:
self.setWindowTitle(program.name)
self.program = program
program.window = weakref.proxy(self)
if hasattr(self,'devicePixelRatio'):
program.view.screenDeviceScale = self.devicePixelRatio()
else:
program.view.screenDeviceScale = 1
if self.initialized:
program.initialize()
program.reshapefunc(self.width,self.height)
def idleCallback():
self.nextIdleEvent = 0
if self.program: self.program.idlefunc()
if self.nextIdleEvent == 0:
self.idleTimer.start(0)
self.idleTimer.timeout.connect(idleCallback)
else:
self.reshape(program.view.w,program.view.h)
[docs] def setParent(self,parent=None):
QGLWidget.setParent(self,parent)
[docs] def initialize(self):
""" Opens a window and initializes. Called internally, and must be in the visualization thread."""
assert self.program != None, "QGLWidget initialized without a GLProgram"
try:
glEnable(GL_MULTISAMPLE)
except Exception:
print("QGLWidget.initialize(): perhaps Qt didn't initialize properly?")
pass
self.setMouseTracking(True)
self.setFocusPolicy(Qt.StrongFocus)
def idleCallback():
self.nextIdleEvent = 0
if self.program: self.program.idlefunc()
if self.nextIdleEvent == 0:
self.idleTimer.start(0)
self.idleTimer = QTimer(self)
self.idleTimer.timeout.connect(idleCallback)
self.idleTimer.setSingleShot(True)
self.idleTimer.start(0)
#init function
self.program.initialize()
if self.actionMenu is not None:
for a in self.actions:
self.actionMenu.addAction(a)
else:
print("QtGLWidget.initialize: no action menu?")
self.initialized = True
[docs] def hide(self):
"""Hides the window, if already shown"""
QGLWidget.hide(self)
if self.initialized:
self.idlesleep()
[docs] def show(self):
"""Restores from a previous hide call"""
QGLWidget.show(self)
if self.initialized:
#boot it back up again
self.idlesleep(0)
self.refresh()
[docs] def close(self):
"""Qt thread should call close() after this widget should be closed down to stop
any existing Qt callbacks."""
if not self.initialized:
return
print("######### QGLWidget close ###############")
self.idleTimer.stop()
self.idleTimer.deleteLater()
self.idleTimer = None
if self.program:
self.program.window = None
self.program = None
self.initialized = False
[docs] def add_action(self,hook,short_text,key=None,description=None):
"""This is called by the user to add actions to the menu bar.
Args:
hook (function): a python callback function, taking no arguments, called
when the action is triggered.
short_text (str): the text shown in the menu bar.
key (str, optional): a shortcut keyboard command (can be 'k' or 'Ctrl+k').
description (str, optional): if provided, this is a tooltip that shows up
when the user hovers their mouse over the menu item.
"""
a = QAction(short_text, self)
if key is not None:
a.setShortcut(key)
if description is None:
description = short_text
a.setStatusTip(description)
a.triggered.connect(hook)
self.actions.append(a)
[docs] def sizeHint(self):
return QSize(self.width,self.height)
#QtGLWidget bindings
[docs] def initializeGL(self):
print("######### QGLWidget Initialize GL ###############")
if self.initialized:
print("QGLWidget.initializeGL: already initialized?")
try:
self.makeCurrent()
return self.initialize()
except Exception as e:
import traceback
print("QGLWidget.initializeGL: hit an exception?")
traceback.print_exc()
exit(-1)
[docs] def resizeGL(self,w,h):
if self.program == None:
print("QGLWidget.resizeGL: called after close?")
return
if not self.isVisible():
return
(self.devwidth,self.devheight) = (w,h)
return
[docs] def paintGL(self):
if self.program == None:
print("QGLWidget.paintGL: called after close?")
return
if not self.isVisible():
print("QGLWidget.paintGL: called while invisible?")
return
if self.inpaint:
return
self.inpaint = True
self.refreshed = False
try:
glRenderMode(GL_RENDER)
res = self.program.displayfunc()
except Exception as e:
import traceback
print("QGLWidget.paintGL: hit an exception?")
traceback.print_exc()
exit(-1)
self.inpaint = False
return
#QWidget bindings
[docs] def mouseMoveEvent(self,e):
if self.program == None:
print("QGLWidget.mouseMoveEvent: called after close?")
return
self.modifierList = toModifierList(e.modifiers())
x,y = e.pos().x(),e.pos().y()
if self.lastx == None: dx,dy = 0,0
else: dx, dy = x - self.lastx, y - self.lasty
try:
res = self.program.motionfunc(x,y,dx,dy)
except Exception as e:
import traceback
traceback.print_exc()
exit(-1)
self.lastx,self.lasty = x,y
[docs] def mousePressEvent(self,e):
x,y = e.pos().x(),e.pos().y()
self.modifierList = toModifierList(e.modifiers())
self.lastx,self.lasty = x,y
self.program.mousefunc(toGlutButton(e.button()),GLUT_DOWN,x,y)
[docs] def mouseReleaseEvent(self,e):
x,y = e.pos().x(),e.pos().y()
self.modifierList = toModifierList(e.modifiers())
self.lastx,self.lasty = x,y
self.program.mousefunc(toGlutButton(e.button()),GLUT_UP,x,y)
[docs] def keyPressEvent(self,e):
if e.isAutoRepeat():
return
if e.key() in keymap:
self.modifierList = toModifierList(e.modifiers())
self.program.keyboardfunc(keymap[e.key()],self.lastx,self.lasty)
return
else:
c = str(e.text())
if len(c)==0: return #some empty press, like shift/control
self.modifierList = toModifierList(e.modifiers())
self.program.keyboardfunc(c,self.lastx,self.lasty)
[docs] def keyReleaseEvent(self,e):
if e.isAutoRepeat():
return
if e.key() in keymap:
self.modifierList = toModifierList(e.modifiers())
self.program.keyboardupfunc(keymap[e.key()],self.lastx,self.lasty)
return
else:
c = str(e.text())
if len(c)==0: return #some empty press, like shift/control
self.modifierList = toModifierList(e.modifiers())
self.program.keyboardupfunc(c,self.lastx,self.lasty)
[docs] def modifiers(self):
"""Call this to retrieve modifiers. Called by frontend."""
return self.modifierList
[docs] @pyqtSlot(float)
def do_idlesleep(self,duration):
if self.idleTimer is None:
import traceback
traceback.print_stack()
raise ValueError("Idlesleep %f was called before initialize"%(duration,))
if duration<=0:
self.nextIdleEvent = 0
self.idleTimer.start(0)
elif duration == float('inf'):
#print "Stopping idle timer",self.name,"forever"
self.idleTimer.stop()
else:
#print "Stopping idle timer",self.name,duration
self.idleTimer.start(int(1000*duration))
self.nextIdleEvent = duration
[docs] def idlesleep(self,duration=float('inf')):
"""Externally callable. Sleeps the idle callback for t seconds. If t is not provided,
the idle callback is slept forever"""
self.idlesleep_signal.emit(duration)
[docs] @pyqtSlot()
def do_refresh(self):
self.makeCurrent()
#self.updateGL()
self.update()
[docs] def refresh(self):
"""Externally callable. Requests a refresh of the window"""
if not self.refreshed:
self.refreshed = True
if not self.isVisible():
return
self.refresh_signal.emit()
[docs] def resizeEvent(self,event):
"""Called internally by Qt when Qt resizes the window"""
QGLWidget.resizeEvent(self,event)
self.width,self.height = (event.size().width(),event.size().height())
if hasattr(self,'devicePixelRatio'):
scale = self.devicePixelRatio()
else:
scale = 1
(self.devwidth,self.devheight) = (self.width*scale,self.height*scale)
#self.window().resize(event.size())
#self.window().adjustSize()
if self.program:
self.program.reshapefunc(self.width,self.height)
self.refresh()
[docs] @pyqtSlot(int,int)
def do_reshape(self,w,h):
(self.width,self.height) = (w,h)
self.setFixedSize(self.width,self.height)
self.window().resize(self.sizeHint())
self.window().adjustSize()
if self.isVisible():
self.do_refresh()
[docs] def reshape(self,w,h):
"""Externally callable. Reshapes the window"""
self.reshape_signal.emit(w,h)
[docs] def draw_text(self,point,text,size=12,color=None):
"""Renders text at the given point (may be 2d or 3d). Frontend should call this to draw text
in either the display or display_screen method.
Args:
point (list of floats): either a 2d or 3d point at which to draw the text
text (str): the text to draw
size (int, optional): if given, it renders a font in the given size.
color (list of 3 floats or list of 4 floats) if given, then an RGB or RGBA color value.
"""
if color:
if len(color)==3:
glColor3f(*color)
else:
glColor4f(*color)
font = QtGui.QFont()
font.setPixelSize(size)
if len(point) == 2:
self.renderText(point[0],point[1],0,text,font)
else:
self.renderText(point[0],point[1],point[2],text,font)
[docs]class QtBackend:
"""
Backend implementation of OpenGL visualization using Qt. Usually hidden from the user.
To use as a standalone program: Set up your GLProgramInterface, call createWindow to
construct new windows and setProgram to set the GLProgram used in that window.
Then call run() to start the Qt main loop.
"""
def __init__(self):
self.app = None
self.window = None
[docs] def initialize(self,program_name):
if self.app == None:
print("QtBackend: initializing app as",program_name)
#this is needed for some X11 multithreading bug
QCoreApplication.setAttribute(Qt.AA_X11InitThreads)
self.app = QApplication([program_name])
[docs] def createWindow(self,name,parent=None):
self.initialize(name)
self.window = QtGLWindow(name,parent)
return self.window
[docs] def run(self):
"""Starts the main loop"""
assert self.window != None, "No windows create()'ed"
self.window.show()
self.app.exec_()
[docs] def kill(self):
if self.window:
self.window.close()
del self.window
del self.app
self.app = None
self.window = None