# HG changeset patch # User drewp@bigasterisk.com # Date 2008-09-01 00:41:29 # Node ID 60b49f6c202775b53e52302d309c26a06821cdb6 # Parent cd4d7b8785507ac78d85c3edf81607b623004ad2 start porting lightsim to qt diff --git a/bin/lightsim b/bin/lightsim --- a/bin/lightsim +++ b/bin/lightsim @@ -2,31 +2,30 @@ from __future__ import division import run_local +import xmlrpclib, sys, logging + +sys.path.append("lib") +import qt4reactor +qt4reactor.install() from rdflib import Literal, URIRef -from twisted.internet import reactor, tksupport +from twisted.internet import reactor from twisted.internet.task import LoopingCall from twisted.web.xmlrpc import Proxy -import xmlrpclib, sys, logging +from louie import dispatcher +from PyQt4 import QtCore, QtGui, QtOpenGL +from OpenGL.GL import * +from OpenGL.GLU import * + +from light9 import networking, Patch, showconfig, dmxclient, updatefreq, prof +from light9.namespaces import L9 +from lightsim.openglsim import Surface + log = logging.getLogger() logging.basicConfig(format="%(asctime)s %(levelname)-5s %(name)s %(filename)s:%(lineno)d: %(message)s") log.setLevel(logging.DEBUG) -import Tkinter as tk -from light9 import networking, Patch, showconfig, dmxclient, updatefreq, prof -from light9.namespaces import L9 -from louie import dispatcher -try: - from OpenGL import Tk as Togl - from OpenGL.GL import * -except ImportError: - sys.path.append("/usr/lib/python2.4/site-packages/OpenGL/Tk/linux2-tk8.4") - from OpenGL.GL import * - import Togl - -from lightsim.openglsim import Surface - -def poll(graph, serv, pollFreq): +def poll(graph, serv, pollFreq, oglSurface): pollFreq.update() dispatcher.send("status", key="pollFreq", value=str(pollFreq)) d = serv.callRemote("currentlevels", dmxclient._id) @@ -45,11 +44,11 @@ def poll(graph, serv, pollFreq): for imgPath in graph.objects(lyr, L9['path']): level[str(imgPath)] = lev - ogl.newLevels(levels=level) + oglSurface.newLevels(levels=level) d.addCallback(received) return d -class StatusKeys(tk.Frame): +class StatusKeys(object): # watch out- this might be an accidental redo of what curvecalc # has. Or, maybe CC should use this obj def __init__(self, master): @@ -69,31 +68,51 @@ class StatusKeys(tk.Frame): row, lab = self.row[key] lab.config(text=value) -root = tk.Frame() -root.pack(expand=True, fill='both') -pollFreq = updatefreq.Updatefreq(samples=5) -graph = showconfig.getGraph() -filenames = [] -for lyr in graph.objects(None, L9['previewLayer']): - for p in graph.objects(lyr, L9['path']): - filenames.append(str(p)) - -ogl = Surface(root, filenames, width=120, height=80, imgRescaleTo=128) -ogl.pack(side='top', expand=True, fill='both') - -sk = StatusKeys(root) -sk.pack(side='top', fill='x') +class Window(QtGui.QMainWindow): + def __init__(self, filenames): + QtGui.QMainWindow.__init__(self, None) -serv = Proxy(networking.dmxServerUrl()) -LoopingCall(poll, graph, serv, pollFreq).start(.1) + self.w = QtGui.QWidget() + self.setCentralWidget(self.w) + + self.glWidget = Surface(self, filenames, + width=120*3, height=80*3, + imgRescaleTo=128) + + self.glWidget.newLevels(levels={ + "lightsim/skyline/front-left.png" : .4, + "lightsim/skyline/front-right.png" : .8}) + + mainLayout = QtGui.QHBoxLayout() + mainLayout.addWidget(self.glWidget) + self.w.setLayout(mainLayout) + + self.setWindowTitle(dmxclient._id) -top = root.winfo_toplevel() -top.wm_title(dmxclient._id) -top.bind("",lambda ev: reactor.stop) -top.bind("",lambda ev: reactor.stop) -top.protocol('WM_DELETE_WINDOW', reactor.stop) -tksupport.install(ogl, ms=20) +if __name__ == '__main__': + app = reactor.qApp + + graph = showconfig.getGraph() + filenames = [] + for lyr in graph.objects(None, L9['previewLayer']): + for p in graph.objects(lyr, L9['path']): + filenames.append(str(p)) + filenames.append("lightsim/skyline/front-left.png") # test + filenames.append("lightsim/skyline/front-right.png") # test -prof.run(reactor.run, profile=False) + window = Window(filenames) + window.show() + + serv = Proxy(networking.dmxServerUrl()) + pollFreq = updatefreq.Updatefreq(samples=5) + LoopingCall(poll, graph, serv, pollFreq, window.glWidget).start(.1) + + reactor.run() + + +###################################################### +#sk = StatusKeys(root) +#sk.pack(side='top', fill='x') +#prof.run(reactor.run, profile=False) diff --git a/lib/qt4reactor.py b/lib/qt4reactor.py new file mode 100644 --- /dev/null +++ b/lib/qt4reactor.py @@ -0,0 +1,213 @@ +# http://twistedmatrix.com/trac/browser/sandbox/therve/qt4reactor.py +# with some fixes by drewp + +# Copyright (c) 2001-2008 Twisted Matrix Laboratories. +# See LICENSE for details. + + +""" +This module provides support for Twisted to interact with the PyQt mainloop. + +In order to use this support, simply do the following:: + + | import qt4reactor + | qt4reactor.install() + +Then use twisted.internet APIs as usual. The other methods here are not +intended to be called directly. + +API Stability: stable + +Maintainer: U{Itamar Shtull-Trauring} +Port to QT4: U{Gabe Rudy} +""" + +__all__ = ['install'] + + +import sys + +from zope.interface import implements + +from PyQt4.QtCore import QSocketNotifier, QObject, SIGNAL, QTimer +from PyQt4.QtGui import QApplication + +from twisted.internet.interfaces import IReactorFDSet +from twisted.python import log +from twisted.internet.posixbase import PosixReactorBase + + + +class TwistedSocketNotifier(QSocketNotifier): + """ + Connection between an fd event and reader/writer callbacks. + """ + + def __init__(self, reactor, watcher, type): + QSocketNotifier.__init__(self, watcher.fileno(), type) + self.reactor = reactor + self.watcher = watcher + self.fn = None + if type == QSocketNotifier.Read: + self.fn = self.read + elif type == QSocketNotifier.Write: + self.fn = self.write + QObject.connect(self, SIGNAL("activated(int)"), self.fn) + + + def shutdown(self): + QObject.disconnect(self, SIGNAL("activated(int)"), self.fn) + self.setEnabled(0) + self.fn = self.watcher = None + + + def read(self, sock): + w = self.watcher + def _read(): + why = None + try: + why = w.doRead() + except: + log.err() + why = sys.exc_info()[1] + if why: + self.reactor._disconnectSelectable(w, why, True) + log.callWithLogger(w, _read) + self.reactor.simulate() + + + def write(self, sock): + w = self.watcher + def _write(): + why = None + self.setEnabled(0) + try: + why = w.doWrite() + except: + log.err() + why = sys.exc_info()[1] + if why: + self.reactor._disconnectSelectable(w, why, False) + elif self.watcher: + self.setEnabled(1) + log.callWithLogger(w, _write) + self.reactor.simulate() + + + +class QTReactor(PosixReactorBase): + """ + Qt based reactor. + """ + implements(IReactorFDSet) + + # Reference to a DelayedCall for self.crash() when the reactor is + # entered through .iterate() + _crashCall = None + + _timer = None + + def __init__(self, app=None): + self._reads = {} + self._writes = {} + if app is None: + app = QApplication([]) + self.qApp = app + PosixReactorBase.__init__(self) + self.addSystemEventTrigger('after', 'shutdown', self.cleanup) + + + + def addReader(self, reader): + if not reader in self._reads: + self._reads[reader] = TwistedSocketNotifier(self, reader, + QSocketNotifier.Read) + + + def addWriter(self, writer): + if not writer in self._writes: + self._writes[writer] = TwistedSocketNotifier(self, writer, + QSocketNotifier.Write) + + + def removeReader(self, reader): + if reader in self._reads: + self._reads[reader].shutdown() + del self._reads[reader] + + + def removeWriter(self, writer): + if writer in self._writes: + self._writes[writer].shutdown() + del self._writes[writer] + + + def removeAll(self): + return self._removeAll(self._reads, self._writes) + + + def getReaders(self): + return self._reads.keys() + + + def getWriters(self): + return self._writes.keys() + + + def simulate(self): + self._lastTimer = self._timer # put off the __del__ + if self._timer is not None: + self._timer.stop() + self._timer = None + + if not self.running: + self.qApp.exit() + return + self.runUntilCurrent() + + if self._crashCall is not None: + self._crashCall.reset(0) + + timeout = self.timeout() + if timeout is None: + timeout = 1.0 + timeout = min(timeout, 0.01) * 1010 + + if self._timer is None: + self._timer = QTimer() + self._timer.setObjectName("simulateTimer") + QObject.connect(self._timer, SIGNAL("timeout()"), self.simulate) + self._timer.start(timeout) + + def cleanup(self): + if self._timer is not None: + self._timer.stop() + self._timer = None + + + def iterate(self, delay=0.0): + self._crashCall = self.callLater(delay, self._crash) + self.run() + + + def mainLoop(self): + self.simulate() + self.qApp.exec_() + + + def _crash(self): + if self._crashCall is not None: + if self._crashCall.active(): + self._crashCall.cancel() + self._crashCall = None + self.running = False + + + +def install(app=None): + """ + Configure the twisted mainloop to be run inside the qt mainloop. + """ + from twisted.internet import main + reactor = QTReactor(app=app) + main.installReactor(reactor) diff --git a/lightsim/openglsim.py b/lightsim/openglsim.py --- a/lightsim/openglsim.py +++ b/lightsim/openglsim.py @@ -7,14 +7,9 @@ import numarray as num import Tkinter as tk import Image from louie import dispatcher -try: - from OpenGL import Tk as Togl - from OpenGL.GL import * -except ImportError: - sys.path.append("/usr/lib/python2.4/site-packages/OpenGL/Tk/linux2-tk8.4") - from OpenGL.GL import * - import Togl - + +from PyQt4 import QtCore, QtOpenGL +from OpenGL.GL import * def xxxdrawWithAlpha(imgString, w, h, alpha): # this one should be faster because GL does the alpha adjust, but @@ -26,22 +21,21 @@ def xxxdrawWithAlpha(imgString, w, h, al #glBlendColor(1, 1, 1, mag) # needs ARB_imaging glDrawPixels(w, h, GL_RGBA, GL_UNSIGNED_BYTE, imgString) -class Surface(Togl.Opengl): +class Surface(QtOpenGL.QGLWidget): """widget that adds multiple image files together with adjustable scales""" - def __init__(self, master, filenames, width=512, height=270, + def __init__(self, parent, filenames, width=512, height=270, imgRescaleTo=None): """ imgRescaleTo can be a length of pixels to reduce all the input images into. Try 64 for a low res drawing. """ - Togl.Opengl.__init__(self, master=master, width=width, - height=height, double=True, depth=0) - self.width, self.height = width, height + QtOpenGL.QGLWidget.__init__(self, parent) self.levels = {} # filename : brightness self.image = {} # filename : imgstr for filename in filenames: + print "open", filename im = Image.open(filename) if imgRescaleTo: im.thumbnail((imgRescaleTo, imgRescaleTo)) @@ -50,20 +44,22 @@ class Surface(Togl.Opengl): self.imageHeight = im.size[1] self.image[filename] = im.convert("RGBA").tostring() - self.set_centerpoint(0, 0, 0) - + #self.set_centerpoint(0, 0, 0) + + def initializeGL(self): glDisable(GL_CULL_FACE) glShadeModel(GL_FLAT) print 'GL_ARB_imaging', 'GL_ARB_imaging' in glGetString(GL_EXTENSIONS) import OpenGL print OpenGL.__version__ - - self.bind("", self.configure) + +# def minimumSizeHint(self): +# return QtCore.QSize(512, 512) - def configure(self, ev): - self.width, self.height = ev.width, ev.height - - def redraw(self, event=None): +# def sizeHint(self): +# return QtCore.QSize(512, 512) + + def paintGL(self): """you set self.levels to dict and call tkRedraw""" assert 'GL_ARB_imaging' in glGetString(GL_EXTENSIONS).split() start = time.time() @@ -80,7 +76,6 @@ class Surface(Togl.Opengl): # drawing to glAccum might be good layerTimes = [] for filename, mag in self.levels.items(): - #print "pic %s at %f" % (filename, mag) t = time.time() self.drawWithAlpha(self.image[filename], self.imageWidth, self.imageHeight, mag) @@ -109,8 +104,8 @@ class Surface(Togl.Opengl): # this right now. #glBlendFunc(GL_CONSTANT_COLOR, GL_ONE) #glBlendColor(.8, .5, .5, .5) - - glPixelZoom(self.width / w, self.height / h) + + glPixelZoom(self.width() / w, self.height() / h) glDrawPixels(w, h, GL_RGBA, GL_UNSIGNED_BYTE, ar.tostring()) #print " draw", time.time() - t @@ -118,8 +113,23 @@ class Surface(Togl.Opengl): def newLevels(self, event=None, levels=None): if levels != self.levels: self.levels = levels - self.tkRedraw() - + self.updateGL() + + +## def mousePressEvent(self, event): +## self.lastPos = QtCore.QPoint(event.pos()) + +## def mouseMoveEvent(self, event): +## dx = event.x() - self.lastPos.x() +## dy = event.y() - self.lastPos.y() +## rot = (.25*dy, .25*dx, 0) +## if event.buttons() & QtCore.Qt.LeftButton: +## self.emit(QtCore.SIGNAL("rotationChanged"), rot) +## elif event.buttons() & QtCore.Qt.MidButton: +## self.emit(QtCore.SIGNAL("camDistChanged"), .01*dy) + +## self.lastPos = QtCore.QPoint(event.pos()) + def main(): root = tk.Frame() root.pack(expand=True, fill='both')