import math import array, itertools import sys from OpenGL.GL import * from OpenGL.GLU import * def use_pango_font(font, start, count, will_call_prepost=False): import gi gi.require_version('Pango','1.0') gi.require_version('PangoCairo','1.0') from gi.repository import Pango from gi.repository import PangoCairo #from gi.repository import Cairo as cairo import cairo fontDesc = Pango.FontDescription(font) a = array.array('b', itertools.repeat(0, 256*256)) surface = cairo.ImageSurface.create_for_data(a, cairo.FORMAT_A8, 256, 256) context = cairo.Context(surface) pango_context = PangoCairo.create_context(context) layout = PangoCairo.create_layout(context) fontmap = PangoCairo.font_map_get_default() font = fontmap.load_font(fontmap.create_context(), fontDesc) layout.set_font_description(fontDesc) metrics = font.get_metrics() descent = metrics.get_descent() d = descent / Pango.SCALE linespace = metrics.get_ascent() + metrics.get_descent() width = metrics.get_approximate_char_width() glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT) glPixelStorei(GL_UNPACK_SWAP_BYTES, 0) glPixelStorei(GL_UNPACK_LSB_FIRST, 1) glPixelStorei(GL_UNPACK_ROW_LENGTH, 256) glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, 256) glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0) glPixelStorei(GL_UNPACK_SKIP_ROWS, 0) glPixelStorei(GL_UNPACK_SKIP_IMAGES, 0) glPixelStorei(GL_UNPACK_ALIGNMENT, 1) glPixelZoom(1, -1) base = glGenLists(count) for i in range(count): ch = chr(start+i) layout.set_text(ch, -1) w, h = layout.get_size() context.save() context.new_path() context.rectangle(0, 0, 256, 256) context.set_source_rgba(0., 0., 0., 0.) context.set_operator (cairo.OPERATOR_SOURCE) context.paint() context.restore() context.save() context.set_source_rgba(1., 1., 1., 1.) context.set_operator (cairo.OPERATOR_SOURCE) context.move_to(0, 0) PangoCairo.update_context(context,pango_context) PangoCairo.show_layout(context,layout) context.restore() w, h = int(w / Pango.SCALE), int(h / Pango.SCALE) glNewList(base+i, GL_COMPILE) glBitmap(0, 0, 0, 0, 0, h-d, None) #glDrawPixels(0, 0, 0, 0, 0, h-d, ''); if not will_call_prepost: pango_font_pre() if w and h: try: pass glDrawPixels(w, h, GL_LUMINANCE, GL_UNSIGNED_BYTE, a.tobytes()) except Exception as e: print("glnav Exception ",e) glBitmap(0, 0, 0, 0, w, -h+d, None) if not will_call_prepost: pango_font_post() glEndList() glPopClientAttrib() return base, int(width / Pango.SCALE), int(linespace / Pango.SCALE) def pango_font_pre(rgba=(1., 1., 0., 1.)): glPushAttrib(GL_COLOR_BUFFER_BIT) glEnable(GL_BLEND) glBlendFunc(GL_ONE, GL_ONE) def pango_font_post(): glPopAttrib() def glTranslateScene(w, 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(w, s, xcenter, ycenter, zcenter, x, y, mousex, mousey): def snap(a): m = a%90 if m < 3: return a-m elif m > 87: return a-m+90 else: return a lat = min(w.maxlat, max(w.minlat, w.lat + (y - mousey) * .5)) lon = (w.lon + (x - mousex) * .5) % 360 glMatrixMode(GL_MODELVIEW) glTranslatef(xcenter, ycenter, zcenter) mat = glGetDoublev(GL_MODELVIEW_MATRIX) glLoadIdentity() tx, ty, tz = mat[3][:3] glTranslatef(tx, ty, tz) glRotatef(snap(lat), *w.rotation_vectors[0]) glRotatef(snap(lon), *w.rotation_vectors[1]) glTranslatef(-xcenter, -ycenter, -zcenter) w.lat = lat w.lon = lon def sub(x, y): return list(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 = list(map(lambda x, y: x-y, p2, p1)) g = list(map(lambda x, y: x-y, x, p1)) return dot(g, g) - dot(f, g)**2/dot(f, f) 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] class GlNavBase: rotation_vectors = [(1.,0.,0.), (0., 0., 1.)] def __init__(self): # 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 # View settings self.perspective = 0 self.lat = 0 self.lon = 0 self.minlat = -90 self.maxlat = 90 # keep track of total translations # since last view reset self._totalx = 0.0 self._totaly = 0.0 # 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() glLightfv(GL_LIGHT0, GL_POSITION, (1, -1, 1, 0)) glLightfv(GL_LIGHT0, GL_AMBIENT, (.4, .4, .4, 1)) glLightfv(GL_LIGHT0, GL_DIFFUSE, (.6, .6, .6, 1)) glEnable(GL_LIGHTING) glEnable(GL_LIGHT0) glDepthFunc(GL_LESS) glEnable(GL_DEPTH_TEST) glMatrixMode(GL_MODELVIEW) glLoadIdentity() 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._redraw() 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._redraw() def set_latitudelimits(self, minlat, maxlat): """Set the new "latitude" limits for rotations.""" if maxlat > 180: return if minlat < -180: return if maxlat <= minlat: return self.maxlat = maxlat self.minlat = minlat self._redraw() def set_eyepoint(self, distance): """Set how far the eye is from the position we are looking.""" self.distance = distance self._redraw() def set_eyepoint_from_extents(self, e1, e2): """Set how far the eye is from the position we are looking based on the screen width and height of a subject.""" w = self.winfo_width() h = self.winfo_height() ztran = max(2.0, e1, e2 * w/h) ** 2 self.set_eyepoint(ztran - self.zcenter) def reset(self): """Reset rotation matrix for this widget.""" glMatrixMode(GL_MODELVIEW) glLoadIdentity() self._redraw() # zero the translations - we will be recentering self._totalx = 0.0 self._totaly = 0.0 def recordMouse(self, x, y): self.xmouse = x self.ymouse = y def startRotate(self, x, y): self.recordMouse(x, y) def scale(self, x, y): """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 newdistance = self.distance * scale if newdistance < 1e-30 or newdistance > 1e30: return self.distance = newdistance self._redraw() self.recordMouse(x, y) def rotate(self, x, y): """Perform rotation of scene.""" self.activate() self.perspective = True glRotateScene(self, 0.5, self.xcenter, self.ycenter, self.zcenter, x, y, self.xmouse, self.ymouse) self._redraw() self.recordMouse(x, y) def translate(self, x, y): """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(self, scale, x, y, self.xmouse, self.ymouse) # keep track of all translations since view reset self._totalx = self._totalx + (x - self.xmouse) self._totaly = self._totaly - (self.ymouse - y) self._redraw() self.recordMouse(x, y) def set_viewangle(self, lat, lon, forcerotate=0): self.lat = lat self.lon = lon if forcerotate or self.perspective: glRotateScene(self, 0.5, self.xcenter, self.ycenter, self.zcenter, 0, 0, 0, 0) self._redraw() def get_viewangle(self): return self.lat, self.lon def get_zoom_distance(self): data = self.distance return data def set_zoom_distance(self,data): self.distance = data self._redraw() def zoomin(self): self.distance = self.distance / 1.1 self._redraw() def zoomout(self): self.distance = self.distance * 1.1 self._redraw() def startZoom(self, y): self.y0 = y self.original_zoom = self.distance def continueZoom(self, y): dy = y - self.y0 self.distance = self.original_zoom * pow(1.25, dy / 16.) self._redraw() def getRotateMode(self): return False def translateOrRotate(self, x, y): if self.getRotateMode(): self.rotate(x, y) else: self.translate(x, y) def rotateOrTranslate(self, x, y): if not self.getRotateMode(): self.rotate(x, y) else: self.translate(x, y) # can be used to get current view position def get_total_translation(self): return self._totalx, self._totaly def set_view_x(self): self.reset() glRotatef(-90, 0, 1, 0) glRotatef(-90, 1, 0, 0) mid, size = self.extents_info() glTranslatef(-mid[0], -mid[1], -mid[2]) self.set_eyepoint_from_extents(size[1], size[2]) self.perspective = False self.lat = -90 self.lon = 270 self._redraw() def set_view_y(self): self.reset() glRotatef(-90, 1, 0, 0) if self.is_lathe(): glRotatef(90, 0, 1, 0) mid, size = self.extents_info() glTranslatef(-mid[0], -mid[1], -mid[2]) self.set_eyepoint_from_extents(size[0], size[2]) self.perspective = False self.lat = -90 self.lon = 0 self._redraw() # lathe backtool display def set_view_y2(self): self.reset() glRotatef(90, 1, 0, 0) glRotatef(90, 0, 1, 0) mid, size = self.extents_info() glTranslatef(-mid[0], -mid[1], -mid[2]) self.set_eyepoint_from_extents(size[0], size[2]) self.perspective = False self.lat = -90 self.lon = 0 self._redraw() def set_view_z(self): self.reset() mid, size = self.extents_info() glTranslatef(-mid[0], -mid[1], -mid[2]) self.set_eyepoint_from_extents(size[0], size[1]) self.perspective = False self.lat = self.lon = 0 self._redraw() def set_view_z2(self): self.reset() glRotatef(-90, 0, 0, 1) mid, size = self.extents_info() glTranslatef(-mid[0], -mid[1], -mid[2]) self.set_eyepoint_from_extents(size[1], size[0]) self.perspective = False self.lat = 0 self.lon = 270 self._redraw() def set_view_p(self): self.reset() self.perspective = True mid, size = self.extents_info() glTranslatef(-mid[0], -mid[1], -mid[2]) size = (size[0] ** 2 + size[1] ** 2 + size[2] ** 2) ** .5 if size > 1e99: size = 5. # in case there are no moves in the preview w = self.winfo_width() h = self.winfo_height() fovx = self.fovy if h == 0 else self.fovy * w / h fov = min(fovx, self.fovy) self.set_eyepoint((size * 1.1 + 1.0) / 2 / math.sin ( fov * math.pi / 180 / 2)) self.lat = -60 self.lon = 335 glRotateScene(self, 1.0, mid[0], mid[1], mid[2], 0, 0, 0, 0) self._redraw() # vim:ts=8:sts=4:sw=4:et: