#!/usr/bin/env python
# This is statement is required by the build system to query build info
if __name__ == '__build__':
raise Exception
# A class that creates an opengl widget.
# Mike Hartshorn
# Department of Chemistry
# University of York, UK
#
from OpenGL.GL import *
from OpenGL.GLU import *
from Tkinter import _default_root
from Tkinter import *
import math
import os,sys
#
def glTranslateScene(s, x, y, mousex, mousey):
glMatrixMode(GL_MODELVIEW)
mat = glGetDoublev(GL_MODELVIEW_MATRIX)
glLoadIdentity()
glTranslatef(s * (x - mousex), s * (mousey - y), 0.0)
glMultMatrixd(mat)
def glRotateScene(s, xcenter, ycenter, zcenter, x, y, mousex, mousey):
glMatrixMode(GL_MODELVIEW)
mat = glGetDoublev(GL_MODELVIEW_MATRIX)
glLoadIdentity()
glTranslatef(xcenter, ycenter, zcenter)
glRotatef(s * (y - mousey), 1., 0., 0.)
glRotatef(s * (x - mousex), 0., 1., 0.)
glTranslatef(-xcenter, -ycenter, -zcenter)
glMultMatrixd(mat)
def sub(x, y):
return map(lambda a, b: a-b, x, y)
def dot(x, y):
t = 0
for i in range(len(x)):
t = t + x[i]*y[i]
return t
def glDistFromLine(x, p1, p2):
f = map(lambda x, y: x-y, p2, p1)
g = map(lambda x, y: x-y, x, p1)
return dot(g, g) - dot(f, g)**2/dot(f, f)
# Keith Junius <junius@chem.rug.nl> provided many changes to Togl
TOGL_NORMAL = 1
TOGL_OVERLAY = 2
def v3distsq(a,b):
d = ( a[0] - b[0], a[1] - b[1], a[2] - b[2] )
return d[0]*d[0] + d[1]*d[1] + d[2]*d[2]
# new version from Daniel Faken (Daniel_Faken@brown.edu) for static
# loading comptability
if _default_root is None:
_default_root = Tk()
# add this file's directory to Tcl's search path
# on Linux Togl is installed in ./linux2-tk8.0
# on Windows (Python2.0) in ./win32-tk8.3
_default_root.tk.call('lappend', 'auto_path', os.path.join( \
os.path.dirname(__file__), \
sys.platform + "-tk" + _default_root.getvar("tk_version")))
try:
_default_root.tk.eval('load {} Togl')
except TclError:
pass
_default_root.tk.call('package', 'require', 'Togl')
# This code is needed to avoid faults on sys.exit()
# [DAA, Jan 1998]
import sys
oldexitfunc = None
if hasattr(sys, 'exitfunc'):
oldexitfunc = sys.exitfunc
def cleanup():
from Tkinter import _default_root,TclError
import Tkinter
try:
if _default_root: _default_root.destroy()
except TclError:
pass
Tkinter._default_root = None
if oldexitfunc: oldexitfunc()
sys.exitfunc = cleanup
# [end DAA]
class Togl(Widget):
"""
Togl Widget
Keith Junius
Department of Biophysical Chemistry
University of Groningen, The Netherlands
Very basic widget which provides access to Togl functions.
N.B. this requires a modified version of Togl 1.5 to gain access to the
extra functionality. This support should be included in Togl 1.6, I hope.
"""
def __init__(self, master=None, cnf={}, **kw):
Widget.__init__(self, master, 'togl', cnf, kw)
def render(self):
return
def swapbuffers(self):
self.tk.call(self._w, 'swapbuffers')
def makecurrent(self):
self.tk.call(self._w, 'makecurrent')
def alloccolor(self, red, green, blue):
return self.tk.getint(self.tk.call(self._w, 'alloccolor', red, green, blue))
def freecolor(self, index):
self.tk.call(self._w, 'freecolor', index)
def setcolor(self, index, red, green, blue):
self.tk.call(self._w, 'setcolor', index, red, green, blue)
def loadbitmapfont(self, fontname):
return self.tk.getint(self.tk.call(self._w, 'loadbitmapfont', fontname))
def unloadbitmapfont(self, fontbase):
self.tk.call(self._w, 'unloadbitmapfont', fontbase)
def uselayer(self, layer):
self.tk.call(self._w, 'uselayer', layer)
def showoverlay(self):
self.tk.call(self._w, 'showoverlay')
def hideoverlay(self):
self.tk.call(self._w, 'hideoverlay')
def existsoverlay(self):
return self.tk.getboolean(self.tk.call(self._w, 'existsoverlay'))
def getoverlaytransparentvalue(self):
return self.tk.getint(self.tk.call(self._w, 'getoverlaytransparentvalue'))
def ismappedoverlay(self):
return self.tk.getboolean(self.tk.call(self._w, 'ismappedoverlay'))
def alloccoloroverlay(self, red, green, blue):
return self.tk.getint(self.tk.call(self._w, 'alloccoloroverlay', red, green, blue))
def freecoloroverlay(self, index):
self.tk.call(self._w, 'freecoloroverlay', index)
class RawOpengl(Widget, Misc):
"""Widget without any sophisticated bindings\
by Tom Schwaller"""
def __init__(self, master=None, cnf={}, **kw):
Widget.__init__(self, master, 'togl', cnf, kw)
self.bind('<Map>', self.tkMap)
self.bind('<Expose>', self.tkExpose)
self.bind('<Configure>', self.tkExpose)
def tkRedraw(self, *dummy):
# This must be outside of a pushmatrix, since a resize event
# will call redraw recursively.
self.update_idletasks()
self.tk.call(self._w, 'makecurrent')
_mode = glGetDoublev(GL_MATRIX_MODE)
try:
glMatrixMode(GL_PROJECTION)
glPushMatrix()
try:
self.redraw()
glFlush()
finally:
glPopMatrix()
finally:
glMatrixMode(_mode)
self.tk.call(self._w, 'swapbuffers')
def tkMap(self, *dummy):
self.tkExpose()
def tkExpose(self, *dummy):
self.tkRedraw()
class Opengl(RawOpengl):
"""\
Tkinter bindings for an Opengl widget.
Mike Hartshorn
Department of Chemistry
University of York, UK
http://www.yorvic.york.ac.uk/~mjh/
"""
def __init__(self, master=None, cnf={}, **kw):
"""\
Create an opengl widget.
Arrange for redraws when the window is exposed or when
it changes size."""
#Widget.__init__(self, master, 'togl', cnf, kw)
apply(RawOpengl.__init__, (self, master, cnf), kw)
self.initialised = 0
# Current coordinates of the mouse.
self.xmouse = 0
self.ymouse = 0
# Where we are centering.
self.xcenter = 0.0
self.ycenter = 0.0
self.zcenter = 0.0
# The _back color
self.r_back = 1.
self.g_back = 0.
self.b_back = 1.
# Where the eye is
self.distance = 10.0
# Field of view in y direction
self.fovy = 30.0
# Position of clipping planes.
self.near = 0.1
self.far = 1000.0
# Is the widget allowed to autospin?
self.autospin_allowed = 0
# Is the widget currently autospinning?
self.autospin = 0
# Basic bindings for the virtual trackball
self.bind('<Map>', self.tkMap)
self.bind('<Expose>', self.tkExpose)
self.bind('<Configure>', self.tkExpose)
self.bind('<Shift-Button-1>', self.tkHandlePick)
#self.bind('<Button-1><ButtonRelease-1>', self.tkHandlePick)
self.bind('<Button-1>', self.tkRecordMouse)
self.bind('<B1-Motion>', self.tkTranslate)
self.bind('<Button-2>', self.StartRotate)
self.bind('<B2-Motion>', self.tkRotate)
self.bind('<ButtonRelease-2>', self.tkAutoSpin)
self.bind('<Button-3>', self.tkRecordMouse)
self.bind('<B3-Motion>', self.tkScale)
def help(self):
"""Help for the widget."""
import Dialog
d = Dialog.Dialog(None, {'title': 'Viewer help',
'text': 'Button-1: Translate\n'
'Button-2: Rotate\n'
'Button-3: Zoom\n'
'Reset: Resets transformation to identity\n',
'bitmap': 'questhead',
'default': 0,
'strings': ('Done', 'Ok')})
def activate(self):
"""Cause this Opengl widget to be the current destination for drawing."""
self.tk.call(self._w, 'makecurrent')
# This should almost certainly be part of some derived class.
# But I have put it here for convenience.
def basic_lighting(self):
"""\
Set up some basic lighting (single infinite light source).
Also switch on the depth buffer."""
self.activate()
light_position = (1, 1, 1, 0)
glLightfv(GL_LIGHT0, GL_POSITION, light_position)
glEnable(GL_LIGHTING)
glEnable(GL_LIGHT0)
glDepthFunc(GL_LESS)
glEnable(GL_DEPTH_TEST)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
def report_opengl_errors(message = "OpenGL error:"):
"""Report any opengl errors that occured while drawing."""
print 'report_opengl_errors is now useless. glGetError replaced by GLexception'
# while 1:
# err_value = glGetError()
# if not err_value: break
# print message, gluErrorString(err_value)
def set_background(self, r, g, b):
"""Change the background colour of the widget."""
self.r_back = r
self.g_back = g
self.b_back = b
self.tkRedraw()
def set_centerpoint(self, x, y, z):
"""Set the new center point for the model.
This is where we are looking."""
self.xcenter = x
self.ycenter = y
self.zcenter = z
self.tkRedraw()
def set_eyepoint(self, distance):
"""Set how far the eye is from the position we are looking."""
self.distance = distance
self.tkRedraw()
def reset(self):
"""Reset rotation matrix for this widget."""
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
self.tkRedraw()
def tkHandlePick(self, event):
"""Handle a pick on the scene."""
if hasattr(self, 'pick'):
# here we need to use glu.UnProject
# Tk and X have their origin top left,
# while Opengl has its origin bottom left.
# So we need to subtract y from the window height to get
# the proper pick position for Opengl
realy = self.winfo_height() - event.y
p1 = gluUnProject(event.x, realy, 0.)
p2 = gluUnProject(event.x, realy, 1.)
if self.pick(self, p1, p2):
"""If the pick method returns true we redraw the scene."""
self.tkRedraw()
def tkRecordMouse(self, event):
"""Record the current mouse position."""
self.xmouse = event.x
self.ymouse = event.y
def StartRotate(self, event):
# Switch off any autospinning if it was happening
self.autospin = 0
self.tkRecordMouse(event)
def tkScale(self, event):
"""Scale the scene. Achieved by moving the eye position.
Dragging up zooms in, while dragging down zooms out
"""
scale = 1 - 0.01 * (event.y - self.ymouse)
# do some sanity checks, scale no more than
# 1:1000 on any given click+drag
if scale < 0.001:
scale = 0.001
elif scale > 1000:
scale = 1000
self.distance = self.distance * scale
self.tkRedraw()
self.tkRecordMouse(event)
def do_AutoSpin(self):
s = 0.5
self.activate()
glRotateScene(0.5, self.xcenter, self.ycenter, self.zcenter, self.yspin, self.xspin, 0, 0)
self.tkRedraw()
if self.autospin:
self.after(10, self.do_AutoSpin)
def tkAutoSpin(self, event):
"""Perform autospin of scene."""
self.after(4)
self.update_idletasks()
# This could be done with one call to pointerxy but I'm not sure
# it would any quicker as we would have to split up the resulting
# string and then conv
x = self.tk.getint(self.tk.call('winfo', 'pointerx', self._w))
y = self.tk.getint(self.tk.call('winfo', 'pointery', self._w))
if self.autospin_allowed:
if x != event.x_root and y != event.y_root:
self.autospin = 1
self.yspin = x - event.x_root
self.xspin = y - event.y_root
self.after(10, self.do_AutoSpin)
def tkRotate(self, event):
"""Perform rotation of scene."""
self.activate()
glRotateScene(0.5, self.xcenter, self.ycenter, self.zcenter, event.x, event.y, self.xmouse, self.ymouse)
self.tkRedraw()
self.tkRecordMouse(event)
def tkTranslate(self, event):
"""Perform translation of scene."""
self.activate()
# Scale mouse translations to object viewplane so object tracks with mouse
win_height = max( 1,self.winfo_height() )
obj_c = ( self.xcenter, self.ycenter, self.zcenter )
win = gluProject( obj_c[0], obj_c[1], obj_c[2])
obj = gluUnProject( win[0], win[1] + 0.5 * win_height, win[2])
dist = math.sqrt( v3distsq( obj, obj_c ) )
scale = abs( dist / ( 0.5 * win_height ) )
glTranslateScene(scale, event.x, event.y, self.xmouse, self.ymouse)
self.tkRedraw()
self.tkRecordMouse(event)
def tkRedraw(self, *dummy):
"""Cause the opengl widget to redraw itself."""
if not self.initialised: return
self.activate()
glPushMatrix() # Protect our matrix
self.update_idletasks()
self.activate()
w = self.winfo_width()
h = self.winfo_height()
glViewport(0, 0, w, h)
# Clear the background and depth buffer.
glClearColor(self.r_back, self.g_back, self.b_back, 0.)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(self.fovy, float(w)/float(h), self.near, self.far)
if 0:
# Now translate the scene origin away from the world origin
glMatrixMode(GL_MODELVIEW)
mat = glGetDoublev(GL_MODELVIEW_MATRIX)
glLoadIdentity()
glTranslatef(-self.xcenter, -self.ycenter, -(self.zcenter+self.distance))
glMultMatrixd(mat)
else:
gluLookAt(self.xcenter, self.ycenter, self.zcenter + self.distance,
self.xcenter, self.ycenter, self.zcenter,
0., 1., 0.)
glMatrixMode(GL_MODELVIEW)
# Call objects redraw method.
self.redraw(self)
glFlush() # Tidy up
glPopMatrix() # Restore the matrix
self.tk.call(self._w, 'swapbuffers')
def tkMap(self, *dummy):
"""Cause the opengl widget to redraw itself."""
self.tkExpose()
def tkExpose(self, *dummy):
"""Redraw the widget.
Make it active, update tk events, call redraw procedure and
swap the buffers. Note: swapbuffers is clever enough to
only swap double buffered visuals."""
self.activate()
if not self.initialised:
self.basic_lighting()
self.initialised = 1
self.tkRedraw()
def tkPrint(self, file):
"""Turn the current scene into PostScript via the feedback buffer."""
self.activate()
|