The intent of this use of glBitmap is to move the raster position without drawing anything. However, it may be required to use None instead of the empty bytes object for this. I couldn't quite verify this as I don't have an affected system. The final argument is the 'bitmap' argument. The use of a NULL bitmap together with nonzero xmove/ymove parameters is explicitly documented as a valid way to move the raster position (including to offscreen positions); doing it with a zero byte bitmap is apparently unsupported in some OpenGL implementation even if the width×height of the bitmap is 0 pixels. https://docs.gl/gl3/glBitmap specifically documents that negative width and height are errors, but doesn't document that a height or width of 0 is invalid.
451 lines
13 KiB
Python
451 lines
13 KiB
Python
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:
|