Source code for klampt.vis.glprogram

"""Internal classes for building 3D GUI programs.

- GLProgram takes care of basic user input.
- GLNavigationProgram allows 3D navigation with the mouse.
- GLRealtimeProgram calls a subclass-defined idle() function roughly on a
  constant time step.
"""

from . import glinit
from OpenGL import GL
from .glviewport import GLViewport
from ..math import so3,se3,vectorops
import math
import time
import warnings

[docs]class GLProgramAction: def __init__(self,hook,short_text,key,description=None): self.hook = hook self.short_text = short_text self.key = key self.description = description if description == None: self.description = short_text
[docs]class GLProgram: """A basic OpenGL visualization, run as part of some _GLBackend. For the most part there is a one-to-one correspondence and the backend just relays the input / drawing messages Assumes that glinit.py has been imported to define _GLBackend. Attributes: name (str): title of the window (only has an effect before calling run()) window: the QtBackend or GLUTBackend instance view (GLViewport): describes the OpenGL viewport. If this is provided to an empty _GLBackend window, the w,h gives a hint to the size of the window. It is then updated by the user and setting the viewport size has no effect on the window. clearColor (list of 4 floats): the RGBA floating point values of the background color. actions (list of GLProgramAction): the list of actions. Must be populated using add_action before init(). """ def __init__(self,name="OpenGL Program"): self.window = None self.name = name self.view = GLViewport() self.clearColor = [1.0,1.0,1.0,0.0] self.actions = []
[docs] def add_action(self,hook,short_text,key,description=None): """Defines a new generic GUI action. The action will be available in a menu in Qt or as keyboard commands in GLUT.""" self.actions.append(GLProgramAction(hook,short_text,key,description))
[docs] def run(self): """Starts a new event loop with this object as the main program. Note: might not return, in the case of GLUT. """ from . import visualization visualization.setWindowTitle(self.name) visualization.run(self)
[docs] def initialize(self): """Called after the GL context is initialized, but before main loop. May be overridden. Users should not call this directly!""" assert self.window != None for a in self.actions: self.window.add_action(a.hook,a.short_text,a.key,a.description) return True
[docs] def refresh(self): """Call this to redraw the screen on the next event loop""" self.window.refresh()
[docs] def modifiers(self): """Retrieves a list of currently pressed keyboard modifiers. Values can be any combination of 'ctrl', 'shift', 'alt'. """ return self.window.modifiers()
[docs] def reshape(self,w,h): """Asks to resize the GL window""" if self.window: return self.window.reshape(w,h) else: self.view.w,self.view.h = w,h
[docs] def reshapefunc(self,w,h): """Called on window resize. May be overridden.""" self.view.w = w self.view.h = h self.refresh() return True
[docs] def print_help(self): #Put your help printouts here print("************** Help **************") print("?: print this help message") for a in self.actions: print(a.key,":",a.description) print("**********************************")
[docs] def keyboardfunc(self,c,x,y): """Called on keypress down. May be overridden. c is either the ASCII/unicode character of the key pressed or a string describing the character (up,down,left,right, home,end,delete,enter,f1,...,f12)""" if c == '?': self.print_help() return True if 'alt' in self.modifiers(): c = 'Alt+'+c if 'ctrl' in self.modifiers(): c = 'Ctrl+'+c for a in self.actions: if c == a.key: a.hook() self.refresh() return True return False
[docs] def keyboardupfunc(self,c,x,y): """Called on keyboard up (if your system allows it). May be overridden.""" return False
[docs] def motionfunc(self,x,y,dx,dy): """Called when the mouse moves on screen. May be overridden.""" return False
[docs] def mousefunc(self,button,state,x,y): """Called when the mouse is clicked. May be overridden.""" return False
[docs] def displayfunc(self): """All OpenGL calls go here. May be overridden, although you may wish to override display() and display_screen() instead.""" if self.view.w == 0 or self.view.h == 0: #hidden? print("GLProgram.displayfunc called on hidden window?") return False self.prepare_GL() self.display() self.prepare_screen_GL() self.display_screen() return True
[docs] def idlefunc(self): """Called on idle. Default value stops all additional idle calls. Must be overridden if you want to do something in the idle loop.""" #print "Sleeping idle from",self.__class__.__name__ self.idlesleep()
[docs] def idlesleep(self,duration=float('inf')): """Sleeps the idle callback for t seconds. If t is not provided, the idle callback is slept forever""" self.window.idlesleep(duration)
[docs] def prepare_GL(self): """Prepare drawing in world coordinate frame """ # Viewport view = self.view ydevice = (self.window.height - view.y - view.h) GL.glViewport(view.x*view.screenDeviceScale,ydevice*view.screenDeviceScale,view.w*view.screenDeviceScale,view.h*view.screenDeviceScale) # Initialize GL.glClearColor(*self.clearColor) GL.glScissor(view.x*view.screenDeviceScale,ydevice*view.screenDeviceScale,view.w*view.screenDeviceScale,view.h*view.screenDeviceScale) GL.glEnable(GL.GL_SCISSOR_TEST); GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT); GL.glEnable(GL.GL_DEPTH_TEST) GL.glEnable(GL.GL_LIGHTING) GL.glEnable(GL.GL_NORMALIZE) GL.glShadeModel(GL.GL_FLAT)
[docs] def prepare_screen_GL(self): """Prepare drawing on screen """ GL.glMatrixMode(GL.GL_PROJECTION) GL.glLoadIdentity() GL.glOrtho(0,self.view.w*self.view.screenDeviceScale,self.view.h*self.view.screenDeviceScale,0,-1,1); GL.glMatrixMode(GL.GL_MODELVIEW) GL.glLoadIdentity()
[docs] def display(self): """Do drawing of objects in world""" return True
[docs] def display_screen(self): """Do drawing of objects on screen""" return True
[docs] def closefunc(self): """Called by the window when it is closed""" return True
[docs] def get_screen(self,format='auto',want_depth=False): """Retrieves a screenshot""" if hasattr(self.window,'makeCurrent'): self.window.makeCurrent() GL.glReadBuffer(GL.GL_FRONT); x,y,w,h = self.view.x*self.view.screenDeviceScale,self.view.y*self.view.screenDeviceScale,self.view.w*self.view.screenDeviceScale,self.view.h*self.view.screenDeviceScale screenshot = GL.glReadPixels( x, y, w, h, GL.GL_RGB, GL.GL_UNSIGNED_BYTE) if format == 'auto': try: import numpy as np format = 'numpy' except ImportError: try: from PIL import Image format = 'Image' except ImportError: format = 'bytes' if format == 'numpy': import numpy as np rgb = np.frombuffer(screenshot,dtype=np.uint8).reshape((h,w,3)) rgb = np.flip(rgb,0) elif format == 'Image': from PIL import Image rgb = Image.frombuffer("RGB", (w, h), screenshot, "raw", "RGB", 0, 0) rgb = rgb.transpose(Image.FLIP_TOP_BOTTOM) else: rgb = (w,h,screenshot) if want_depth: n,f = self.view.clippingplanes depthdata = GL.glReadPixels( x, y, w, h, GL.GL_DEPTH_COMPONENT, GL.GL_FLOAT) if format == 'numpy': import numpy as np depth = np.frombuffer(depthdata,dtype=np.float32).reshape((h,w)) depth = np.flip(depth,0) depth = (n*f)/(f - depth*(f-n)) elif format == 'Image': from PIL import Image,ImageMath depth = Image.frombuffer("F", (w, h), depthdata, "raw", "F", 0, 0) depth = depth.transpose(Image.FLIP_TOP_BOTTOM) depth = ImageMath.eval("%f / (%f - a*%f)"%(n*f,f,f-n),a=depth) else: import struct deptharray = [struct.unpack("<f",depthdata[i:i+4]) for i in range(0,w*h*4,4)] for i in range(w*h): deptharray[i] = n*f/(f - deptharray[i]*(f-n)) depth = (w,h,deptharray) return (rgb,depth) else: return rgb
[docs] def save_screen(self,fn,multithreaded=True): """Saves a screenshot""" try: from PIL import Image except ImportError: try: import Image except ImportError: warnings.warn("Cannot save screens to disk, the Python Imaging Library is not installed") return im = self.get_screen('Image') print("Saving screen to",fn) if not multithreaded: im.save(fn) else: import threading def func(im,fn): im.save(fn) th = threading.Thread(target=func,args=(im,fn)) th.start()
[docs] def draw_text(self,point,text,size=12,color=None): self.window.draw_text(point,text,size,color)
[docs]class GLNavigationProgram(GLProgram): """A more advanced form of GLProgram that allows you to navigate a camera around a 3D world. Click-drag rotates, Control-drag translates, Shift-drag zooms. """ def __init__(self,name): GLProgram.__init__(self,name) #mouse state information self.dragging = False self.clearColor = [0.8,0.8,0.9,0]
[docs] def get_view(self): """Returns a GLViewport describing the viewport, which could be saved to file.""" return self.view
[docs] def set_view(self,v): """Sets the viewport to a tuple previously returned by get_view(), e.g. a prior view that was saved to file.""" if v.h != self.view.h or v.w != self.view.w: #minimize possible flickering? self.reshape(self.view.w,self.view.h) self.view = v
[docs] def prepare_GL(self): """Prepares for OpenGL rendering with the current modelview matrix and default lights.""" GLProgram.prepare_GL(self) self.view.set_current_GL() self.set_lights_GL()
[docs] def set_lights_GL(self): """Sets the default OpenGL lights""" GL.glLightfv(GL.GL_LIGHT0,GL.GL_POSITION,[0,-1,2,0]) GL.glLightfv(GL.GL_LIGHT0,GL.GL_AMBIENT,[0.05,0.05,0.05,1]) GL.glLightfv(GL.GL_LIGHT0,GL.GL_DIFFUSE,[1,1,1,1]) GL.glLightfv(GL.GL_LIGHT0,GL.GL_SPECULAR,[1,1,1,1]) GL.glEnable(GL.GL_LIGHT0) GL.glLightfv(GL.GL_LIGHT1,GL.GL_POSITION,[-1,2,1,0]) GL.glLightfv(GL.GL_LIGHT1,GL.GL_DIFFUSE,[0.5,0.5,0.5,1]) GL.glLightfv(GL.GL_LIGHT1,GL.GL_SPECULAR,[0.5,0.5,0.5,1]) GL.glEnable(GL.GL_LIGHT1)
[docs] def motionfunc(self,x,y,dx,dy): if self.dragging: if 'ctrl' in self.modifiers(): R,t = self.view.camera.matrix() aspect = float(self.view.w)/self.view.h rfov = math.radians(self.view.fov) scale = 2.0*math.tan(rfov*0.5/aspect)*aspect delta = so3.apply(R,[-scale*float(dx)*self.view.camera.dist/self.view.w,scale*float(dy)*self.view.camera.dist/self.view.w,0]) self.view.camera.tgt = vectorops.add(self.view.camera.tgt,delta) elif 'shift' in self.modifiers(): self.view.camera.dist *= math.exp(dy*0.01) else: self.view.camera.rot[2] -= float(dx)*0.01 self.view.camera.rot[1] -= float(dy)*0.01 self.refresh() return True return False
[docs] def mousefunc(self,button,state,x,y): if button == 0: if state == 0: self.dragging = True else: self.dragging = False return True return False
[docs]class GLRealtimeProgram(GLNavigationProgram): """A GLNavigationProgram that refreshes the screen at a given frame rate. Attributes: ttotal (float): total elapsed time assuming a constant frame rate fps (float): the frame rate in Hz dt (float): 1.0/fps counter (int): a frame counter lasttime (float): time.time() value on the last frame. """ def __init__(self,name): GLNavigationProgram.__init__(self,name) self.ttotal = 0.0 self.fps = 50 self.dt = 1.0/self.fps self.counter = 0 self.lasttime = time.time() # idle callback
[docs] def idlefunc (self): tcur = time.time() tsleep = self.dt - (tcur - self.lasttime) if tsleep > 0.001: #print "Elapsed time",tcur-self.lasttime,"sleep",tsleep,"window",self.window.name self.idlesleep(tsleep) return self.ttotal += self.dt self.counter += 1 #do something random self.idle() self.lasttime = tcur self.refresh() return True
[docs] def idle(self): pass
[docs]class GLPluginProgram(GLRealtimeProgram): """This base class should be used with a GLPluginBase object to handle the GUI functionality (see glcommon.py). Call setPlugin() on this object to set the currently used plugin. pushPlugin()/popPlugin() can also be used to set a hierarchy of plugins.""" def __init__(self,name="GLPluginProgram"): GLRealtimeProgram.__init__(self,name) self.plugins = []
[docs] def setPlugin(self,plugin): warnings.warn("setPlugin will be deprecated in favor of set_plugin in a future version of Klampt",DeprecationWarning) return self.set_plugin(plugin)
[docs] def pushPlugin(self,plugin): warnings.warn("pushPlugin will be deprecated in favor of push_plugin in a future version of Klampt",DeprecationWarning) return self.push_plugin(plugin)
[docs] def popPlugin(self): warnings.warn("popPlugin will be deprecated in favor of pop_plugin in a future version of Klampt",DeprecationWarning) return self.pop_plugin()
[docs] def set_plugin(self,plugin): #first, detatch existing plugins import copy for p in self.plugins: p.window = None p.view = copy.copy(p.view) #now just set this plugin self.plugins = [] if plugin: self.push_plugin(plugin)
[docs] def push_plugin(self,plugin): self.plugins.append(plugin) plugin.window = self.window if self.window: if self.window.initialized: print("GLPluginProgram.pushPlugin called after window was initialized, some actions may not be available") plugin.view = self.view plugin.reshapefunc(self.view.w,self.view.h) self.refresh() elif len(self.plugins) == 1 and hasattr(plugin,'view') and plugin.view != None: self.view = plugin.view else: plugin.view = self.view
[docs] def pop_plugin(self): import copy if len(self.plugins)==0: return None res = self.plugins[-1] self.plugins.pop(-1) res.window = None res.view = copy.copy(res.view) if self.window: self.refresh() return res
[docs] def set_view(self,v): GLRealtimeProgram.set_view(self,v) for p in self.plugins: p.view = self.view
[docs] def initialize(self): #print "GLPluginProgram initialize:",len(self.plugins),"plugins" for plugin in self.plugins: plugin.window = self.window if not plugin.initialize(): warnings.warn("GLPluginProgram.initialize(): Plugin of type",plugin.__class__.__name__,"Did not initialize") return False if hasattr(plugin,'actions'): #print "Adding",len(plugin.actions),"actions for plugin",plugin.__class__.__name__ for a in plugin.actions: self.add_action(*a) return GLRealtimeProgram.initialize(self)
[docs] def idle(self): anyhandled = False for plugin in self.plugins: if hasattr(plugin,'idle') and plugin.idle(): anyhandled = True if not anyhandled: return False return True
[docs] def reshapefunc(self,w,h): GLRealtimeProgram.reshapefunc(self,w,h) for plugin in self.plugins: plugin.reshapefunc(w,h) return
[docs] def keyboardfunc(self,c,x,y): for plugin in self.plugins[::-1]: if plugin.keyboardfunc(c,x,y): return True return GLRealtimeProgram.keyboardfunc(self,c,x,y)
[docs] def keyboardupfunc(self,c,x,y): for plugin in self.plugins[::-1]: if plugin.keyboardupfunc(c,x,y): return True return GLRealtimeProgram.keyboardupfunc(self,c,x,y)
[docs] def motionfunc(self,x,y,dx,dy): for plugin in self.plugins[::-1]: if plugin.motionfunc(x,y,dx,dy): return True return GLRealtimeProgram.motionfunc(self,x,y,dx,dy)
[docs] def mousefunc(self,button,state,x,y): for plugin in self.plugins[::-1]: if plugin.mousefunc(button,state,x,y): return True return GLRealtimeProgram.mousefunc(self,button,state,x,y)
[docs] def displayfunc(self): for plugin in self.plugins[::-1]: if plugin.displayfunc(): return GLRealtimeProgram.displayfunc(self)
[docs] def display(self): for plugin in self.plugins: if plugin.display(): return True return GLRealtimeProgram.display(self)
[docs] def display_screen(self): for plugin in self.plugins: if plugin.display_screen(): return True return GLRealtimeProgram.display_screen(self)