├── pyopengltk ├── darwin.py ├── __init__.py ├── win32.py ├── base.py ├── linux.py └── opengl.py ├── setup.cfg ├── .gitignore ├── LICENSE ├── examples ├── cube.py ├── tkinter_f3d.py ├── demo.py └── shader_example.py ├── setup.py └── README.md /pyopengltk/darwin.py: -------------------------------------------------------------------------------- 1 | """Currently not implemented""" 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | license_files = 3 | LICENSE 4 | 5 | [bdist_wheel] 6 | # build universal wheels by default 7 | universal=1 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # python 2 | *.pyc 3 | __pycache__ 4 | /dist 5 | /build 6 | *.egg-info/ 7 | 8 | # Virtualenv 9 | /env/ 10 | /venv/ 11 | /.venv/ 12 | /.venv2/ 13 | /.venv3/ 14 | 15 | # IDEs 16 | /.idea 17 | /.vscode 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Jonathan Wright 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /examples/cube.py: -------------------------------------------------------------------------------- 1 | import tkinter 2 | from pyopengltk import OpenGLFrame 3 | from OpenGL import GL 4 | from OpenGL import GLU 5 | 6 | verticies = ( (1, -1, -1), (1, 1, -1), (-1, 1, -1), (-1, -1, -1), 7 | (1, -1, 1), (1, 1, 1), (-1, -1, 1), (-1, 1, 1) ) 8 | 9 | edges = ( (0,1), (0,3), (0,4), (2,1), (2,3), (2,7), 10 | (6,3), (6,4), (6,7), (5,1), (5,4), (5,7) ) 11 | 12 | def Cube(): 13 | GL.glBegin(GL.GL_LINES) 14 | for edge in edges: 15 | for vertex in edge: 16 | GL.glVertex3fv(verticies[vertex]) 17 | GL.glEnd() 18 | 19 | class CubeSpinner( OpenGLFrame ): 20 | def initgl(self): 21 | GL.glLoadIdentity() 22 | GLU.gluPerspective(45, (self.width/self.height), 0.1, 50.0) 23 | GL.glTranslatef(0.0,0.0, -5) 24 | def redraw(self): 25 | GL.glRotatef(1, 3, 1, 1) 26 | GL.glClear(GL.GL_COLOR_BUFFER_BIT|GL.GL_DEPTH_BUFFER_BIT) 27 | Cube() 28 | 29 | def main(): 30 | frm = CubeSpinner( height = 600, width = 800 ) 31 | frm.animate = 10 32 | frm.pack( fill = tkinter.BOTH, expand = 1) 33 | return frm.mainloop() 34 | 35 | if __name__=="__main__": 36 | main() 37 | -------------------------------------------------------------------------------- /examples/tkinter_f3d.py: -------------------------------------------------------------------------------- 1 | """This code is free to use example on using pyopengltk """ 2 | """Made by Juha-Pekka Ahto """ 3 | """Machine and production engineer """ 4 | import f3d 5 | import tkinter as tk # Works also on CustomTKinter 6 | from pyopengltk import OpenGLFrame 7 | 8 | 9 | class Frame(OpenGLFrame): 10 | # !!!OpenGlFrame requires adding own code to initgl and redraw!!! 11 | # !!!This solution only renders the F3D viewer, as it uses EXTERNAL, so controls need to be defined!!! 12 | def __init__(self): 13 | super().__init__() 14 | self.mEngine = None 15 | 16 | # Initialize F3D 17 | def initgl(self): 18 | self.mEngine = f3d.Engine(f3d.Window.Type.EXTERNAL) 19 | self.mEngine.loader.load_geometry(f3d.Mesh( 20 | points=[0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0], 21 | face_sides=[3], 22 | face_indices=[0, 1, 2], 23 | ) 24 | ) 25 | 26 | def redraw(self): 27 | self.mEngine.window.render() 28 | 29 | 30 | # Create main window and define size, position and title 31 | root = tk.Tk() 32 | root.geometry('640x480+100+100') 33 | root.title('LanPtr3D') 34 | F3D = Frame() 35 | F3D.pack(fill=tk.BOTH, expand=tk.YES) 36 | 37 | # Run TKinter mainloop 38 | root.mainloop() 39 | -------------------------------------------------------------------------------- /pyopengltk/__init__.py: -------------------------------------------------------------------------------- 1 | # An opengl frame for pyopengl-tkinter based on ctypes (no togl compilation) 2 | # 3 | # Collected together by Jon Wright, Jan 2018. 4 | # 5 | # Based on the work of others: 6 | # 7 | # C + Tcl/Tk 8 | # http://github.com/codeplea/opengl-tcltk/ 9 | # (zlib license) 10 | # Article at: 11 | # https://codeplea.com/opengl-with-c-and-tcl-tk 12 | # 13 | # Python + Tkinter (no pyopengl) 14 | # http://github.com/arcanosam/pytkogl/ 15 | # (The Code Project Open License) 16 | # Article at 17 | # http://www.codeproject.com/Articles/1073475/OpenGL-in-Python-with-TKinter 18 | # 19 | # Large parts copied from pyopengl/Tk/__init__.py 20 | 21 | __author__ = "Jon Wright" 22 | __version__ = "0.0.3" 23 | 24 | import sys 25 | 26 | # Platform specific frames 27 | if sys.platform.startswith('linux'): 28 | from pyopengltk.linux import OpenGLFrame 29 | 30 | if sys.platform.startswith('win32'): 31 | from pyopengltk.win32 import OpenGLFrame 32 | 33 | # if sys.platform.startswith('darwin'): 34 | # from pyopengltk.darwin import OpenGLFrame 35 | 36 | # opengl 37 | from pyopengltk.opengl import RawOpengl 38 | from pyopengltk.opengl import Opengl 39 | from pyopengltk.opengl import glTranslateScene 40 | from pyopengltk.opengl import glRotateScene 41 | from pyopengltk.opengl import v3distsq 42 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='pyopengltk', 5 | version='0.0.4', 6 | author='Jon Wright', 7 | author_email='jonathan.wright@gmail.com', 8 | url='http://github.com/jonwright/pyopengltk', 9 | license='MIT', 10 | description="An opengl frame for pyopengl-tkinter based on ctype", 11 | long_description=open('README.md').read(), 12 | long_description_content_type='text/markdown', 13 | packages=['pyopengltk'], 14 | install_requires=[ 15 | 'pyopengl', 16 | ], 17 | keywords=['opengl', 'window', 'context', 'tk', 'tkinter'], 18 | classifiers=[ 19 | 'License :: OSI Approved :: MIT License', 20 | 'Environment :: Win32 (MS Windows)', 21 | 'Environment :: X11 Applications', 22 | 'Intended Audience :: Developers', 23 | 'Topic :: Multimedia :: Graphics', 24 | 'Topic :: Multimedia :: Graphics :: 3D Rendering', 25 | 'Topic :: Scientific/Engineering :: Visualization', 26 | 'Topic :: Software Development :: Libraries :: Python Modules', 27 | 'Programming Language :: Python :: 2', 28 | 'Programming Language :: Python :: 2.7', 29 | 'Programming Language :: Python :: 3', 30 | 'Programming Language :: Python :: 3.5', 31 | 'Programming Language :: Python :: 3.6', 32 | 'Programming Language :: Python :: 3.7', 33 | ] 34 | ) 35 | -------------------------------------------------------------------------------- /examples/demo.py: -------------------------------------------------------------------------------- 1 | 2 | from __future__ import print_function 3 | 4 | """ 5 | Demo entry point for Tkinter Window with OpenGL 6 | """ 7 | 8 | import sys, math, time 9 | if sys.version_info[0] < 3: 10 | from Tkinter import Tk, YES, BOTH 11 | else: 12 | from tkinter import Tk, YES, BOTH 13 | from OpenGL import GL, GLU 14 | from pyopengltk import OpenGLFrame 15 | 16 | 17 | class AppOgl(OpenGLFrame): 18 | 19 | def initgl(self): 20 | GL.glViewport(0, 0, self.width, self.height) 21 | GL.glClearColor(1.0, 1.0, 1.0, 0.0) 22 | GL.glColor3f(0.0, 0.0, 0.0) 23 | GL.glPointSize(4.0) 24 | GL.glMatrixMode(GL.GL_PROJECTION) 25 | GL.glLoadIdentity() 26 | GLU.gluOrtho2D(-5, 5, -5, 5) 27 | self.start = time.time() 28 | self.nframes = 0 29 | 30 | def redraw(self): 31 | GL.glClear(GL.GL_COLOR_BUFFER_BIT) 32 | GL.glBegin(GL.GL_POINTS) 33 | npt = 100 34 | for i in range(npt): 35 | x = -5.0 + i * 10.0 / npt 36 | y = math.sin(x + time.time())*5/2 37 | GL.glVertex2f(x, y) 38 | GL.glEnd() 39 | GL.glFlush() 40 | self.nframes += 1 41 | tm = time.time() - self.start 42 | print("fps", self.nframes / tm, end="\r") 43 | 44 | 45 | if __name__ == '__main__': 46 | root = Tk() 47 | app = AppOgl(root, width=320, height=200) 48 | app.pack(fill=BOTH, expand=YES) 49 | app.animate = 1 50 | app.after(100, app.printContext) 51 | app.mainloop() 52 | -------------------------------------------------------------------------------- /pyopengltk/win32.py: -------------------------------------------------------------------------------- 1 | """ 2 | Windows implementation of the opengl frame 3 | """ 4 | from ctypes import WinDLL, c_void_p 5 | from ctypes.wintypes import HDC 6 | from OpenGL.WGL import PIXELFORMATDESCRIPTOR, ChoosePixelFormat, \ 7 | SetPixelFormat, SwapBuffers, wglCreateContext, wglMakeCurrent 8 | 9 | from pyopengltk.base import BaseOpenGLFrame 10 | 11 | _user32 = WinDLL('user32') 12 | GetDC = _user32.GetDC 13 | GetDC.restype = HDC 14 | GetDC.argtypes = [c_void_p] 15 | 16 | pfd = PIXELFORMATDESCRIPTOR() 17 | PFD_TYPE_RGBA = 0 18 | PFD_MAIN_PLANE = 0 19 | PFD_DOUBLEBUFFER = 0x00000001 20 | PFD_DRAW_TO_WINDOW = 0x00000004 21 | PFD_SUPPORT_OPENGL = 0x00000020 22 | pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER 23 | pfd.iPixelType = PFD_TYPE_RGBA 24 | pfd.cColorBits = 24 25 | pfd.cDepthBits = 16 26 | pfd.iLayerType = PFD_MAIN_PLANE 27 | 28 | 29 | # Inherits the base and fills in the 3 platform dependent functions 30 | class OpenGLFrame(BaseOpenGLFrame): 31 | 32 | def __init__(self, *args, **kw): 33 | super().__init__(*args, **kw) 34 | 35 | def tkCreateContext(self): 36 | self.__window = GetDC(self.winfo_id()) 37 | pixelformat = ChoosePixelFormat(self.__window, pfd) 38 | SetPixelFormat(self.__window, pixelformat, pfd) 39 | self.__context = wglCreateContext(self.__window) 40 | wglMakeCurrent(self.__window, self.__context) 41 | 42 | def tkMakeCurrent(self): 43 | if self.winfo_ismapped(): 44 | wglMakeCurrent(self.__window, self.__context) 45 | 46 | def tkSwapBuffers(self): 47 | if self.winfo_ismapped(): 48 | SwapBuffers(self.__window) 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pyopengltk 2 | 3 | Tkinter - OpenGL Frame using ctypes 4 | 5 | * [pyopengltk on Github](https://github.com/jonwright/pyopengltk) 6 | * [pyopengltk on PyPI](https://pypi.org/project/pyopengltk/) 7 | 8 | An opengl frame for pyopengl-tkinter based on ctypes (no togl compilation) 9 | 10 | Collected together by Jon Wright, Jan 2018. 11 | 12 | ## Basic Example 13 | 14 | This example creates a window containing an `OpenGLFrame` 15 | filling the entire window. We configure it to animate 16 | (constantly redraw) clearing the screen using a green color. 17 | A simple framerate counter is included. 18 | The context information is printed to the terminal. 19 | 20 | ```python 21 | import time 22 | import tkinter 23 | from OpenGL import GL 24 | from pyopengltk import OpenGLFrame 25 | 26 | class AppOgl(OpenGLFrame): 27 | 28 | def initgl(self): 29 | """Initalize gl states when the frame is created""" 30 | GL.glViewport(0, 0, self.width, self.height) 31 | GL.glClearColor(0.0, 1.0, 0.0, 0.0) 32 | self.start = time.time() 33 | self.nframes = 0 34 | 35 | def redraw(self): 36 | """Render a single frame""" 37 | GL.glClear(GL.GL_COLOR_BUFFER_BIT) 38 | tm = time.time() - self.start 39 | self.nframes += 1 40 | print("fps",self.nframes / tm, end="\r" ) 41 | 42 | 43 | if __name__ == '__main__': 44 | root = tkinter.Tk() 45 | app = AppOgl(root, width=320, height=200) 46 | app.pack(fill=tkinter.BOTH, expand=tkinter.YES) 47 | app.animate = 1 48 | app.after(100, app.printContext) 49 | app.mainloop() 50 | ``` 51 | 52 | The repository on Github also contains more examples. 53 | 54 | ## Install 55 | 56 | From PyPI: 57 | 58 | ``` 59 | pip install pyopengltk 60 | ``` 61 | 62 | From source: 63 | 64 | ``` 65 | git clone https://github.com/jonwright/pyopengltk 66 | cd pyopengltk 67 | pip install . 68 | ``` 69 | 70 | ## Attributions 71 | 72 | Based on the work of others. 73 | 74 | ### C + Tcl/Tk example: 75 | 76 | * Project URL : http://github.com/codeplea/opengl-tcltk/ (zlib license) 77 | * Article at : https://codeplea.com/opengl-with-c-and-tcl-tk 78 | 79 | ### Python + Tkinter (no pyopengl) example: 80 | 81 | * Project URL : http://github.com/arcanosam/pytkogl/ (The Code Project Open License) 82 | * Article at: http://www.codeproject.com/Articles/1073475/OpenGL-in-Python-with-TKinter 83 | 84 | ### pyopengl 85 | 86 | * Large regions of code copied from `pyopengl/Tk/__init__.py`. 87 | -------------------------------------------------------------------------------- /pyopengltk/base.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import sys 3 | from OpenGL import GL 4 | 5 | if sys.version_info[0] < 3: 6 | import Tkinter as tk 7 | import Dialog as dialog 8 | else: 9 | import tkinter as tk 10 | from tkinter import dialog as dialog 11 | 12 | 13 | class BaseOpenGLFrame(tk.Frame): 14 | """ Common code for windows/x11 """ 15 | def __init__(self, *args, **kw): 16 | # Set background to empty string to avoid 17 | # flickering overdraw by Tk 18 | kw['bg'] = "" 19 | tk.Frame.__init__(self, *args, **kw) 20 | self.bind('', self.tkMap) 21 | self.bind('', self.tkResize) 22 | self.bind('', self.tkExpose) 23 | self.animate = 0 24 | self.cb = None 25 | self.context_created = False 26 | 27 | def tkMap(self, evt): 28 | """" Called when frame goes onto the screen """ 29 | self._wid = self.winfo_id() 30 | if not self.context_created: 31 | self.tkCreateContext() 32 | self.initgl() 33 | self.context_created = True 34 | 35 | def printContext(self, extns=False): 36 | """ For debugging """ 37 | exts = GL.glGetString(GL.GL_EXTENSIONS) 38 | if extns: 39 | print("Extension list:") 40 | for e in sorted(exts.split()): 41 | print("\t", e) 42 | else: 43 | print("Number of extensions:", len(exts.split())) 44 | 45 | print("GL_VENDOR :", GL.glGetString(GL.GL_VENDOR)) 46 | print("GL_RENDERER:", GL.glGetString(GL.GL_RENDERER)) 47 | print("GL_VERSION :", GL.glGetString(GL.GL_VERSION)) 48 | try: 49 | print(" GL_MAJOR_VERSION:", GL.glGetIntegerv(GL.GL_MAJOR_VERSION)) 50 | print(" GL_MINOR_VERSION:", GL.glGetIntegerv(GL.GL_MINOR_VERSION)) 51 | print(" GL_SHADING_LANGUAGE_VERSION :", 52 | GL.glGetString(GL.GL_SHADING_LANGUAGE_VERSION)) 53 | msk = GL.glGetIntegerv(GL.GL_CONTEXT_PROFILE_MASK) 54 | print(" GL_CONTEXT_CORE_PROFILE_BIT :", 55 | bool(msk & GL.GL_CONTEXT_CORE_PROFILE_BIT)) 56 | print(" GL_CONTEXT_COMPATIBILITY_PROFILE_BIT :", 57 | bool(msk & GL.GL_CONTEXT_COMPATIBILITY_PROFILE_BIT)) 58 | except: 59 | print("Old context errors arose") 60 | # raise 61 | 62 | def tkCreateContext(self): 63 | # Platform dependent part 64 | raise NotImplementedError 65 | 66 | def tkMakeCurrent(self): 67 | # Platform dependent part 68 | raise NotImplementedError 69 | 70 | def tkSwapBuffers(self): 71 | # Platform dependent part 72 | raise NotImplementedError 73 | 74 | def tkExpose(self, evt): 75 | if self.cb: 76 | self.after_cancel(self.cb) 77 | self._display() 78 | 79 | def tkResize(self, evt): 80 | """ 81 | Things to do on window resize: 82 | Adjust viewport: 83 | glViewPort(0,0, width, height) 84 | Adjust projection matrix: 85 | glFrustum(left * ratio, right * ratio, bottom, top, nearClip,farClip) 86 | or 87 | glOrtho(left * ratio, right * ratio, bottom, top, nearClip,farClip) 88 | or 89 | gluOrtho2D(left * ratio, right * ratio, bottom, top) 90 | (assuming that left, right, bottom and top are all equal and 91 | ratio=width/height) 92 | """ 93 | self.width, self.height = evt.width, evt.height 94 | if self.winfo_ismapped(): 95 | GL.glViewport(0, 0, self.width, self.height) 96 | self.initgl() 97 | 98 | def _display(self): 99 | self.update_idletasks() 100 | self.tkMakeCurrent() 101 | self.redraw() 102 | self.tkSwapBuffers() 103 | if self.animate > 0: 104 | self.cb = self.after(self.animate, self._display) 105 | 106 | def initgl(self): 107 | # For the user code 108 | raise NotImplementedError 109 | 110 | def redraw(self): 111 | # For the user code 112 | raise NotImplementedError 113 | -------------------------------------------------------------------------------- /examples/shader_example.py: -------------------------------------------------------------------------------- 1 | """Example rotating a point cloud containing 100.000 points""" 2 | from __future__ import print_function, division 3 | 4 | from OpenGL import GL, GLUT 5 | import OpenGL.GL.shaders 6 | import ctypes 7 | import types 8 | import numpy 9 | import pyopengltk 10 | import sys 11 | import time 12 | if sys.version_info[0] > 2: 13 | import tkinter as tk 14 | else: 15 | import Tkinter as tk 16 | 17 | 18 | # Avoiding glitches in pyopengl-3.0.x and python3.4 19 | def bytestr(s): 20 | return s.encode("utf-8") + b"\000" 21 | 22 | 23 | # Avoiding glitches in pyopengl-3.0.x and python3.4 24 | def compileShader(source, shaderType): 25 | """ 26 | Compile shader source of given type 27 | source -- GLSL source-code for the shader 28 | shaderType -- GLenum GL_VERTEX_SHADER, GL_FRAGMENT_SHADER, etc, 29 | returns GLuint compiled shader reference 30 | raises RuntimeError when a compilation failure occurs 31 | """ 32 | if isinstance(source, str): 33 | source = [source] 34 | elif isinstance(source, bytes): 35 | source = [source.decode('utf-8')] 36 | 37 | shader = GL.glCreateShader(shaderType) 38 | GL.glShaderSource(shader, source) 39 | GL.glCompileShader(shader) 40 | result = GL.glGetShaderiv(shader, GL.GL_COMPILE_STATUS) 41 | if not(result): 42 | # TODO: this will be wrong if the user has 43 | # disabled traditional unpacking array support. 44 | raise RuntimeError( 45 | """Shader compile failure (%s): %s""" % ( 46 | result, 47 | GL.glGetShaderInfoLog(shader), 48 | ), 49 | source, 50 | shaderType, 51 | ) 52 | return shader 53 | 54 | 55 | vertex_shader = """#version 130 56 | in vec3 position; 57 | varying vec3 vertex_color; 58 | uniform mat3 proj; 59 | void main() 60 | { 61 | gl_Position = vec4( proj*position, 1.0); 62 | gl_PointSize = 4./(0.5 + length( position )); 63 | vertex_color = vec3( position.x/2+.5, position.y/2+.5, position.z/2+.5); 64 | } 65 | """ 66 | 67 | fragment_shader = """#version 130 68 | varying vec3 vertex_color; 69 | void main() 70 | { 71 | gl_FragColor = vec4(vertex_color,0.25f); 72 | } 73 | """ 74 | NPTS = 100000 75 | 76 | vertices = (numpy.random.random(NPTS * 3).astype(numpy.float32)-.5) * 1.5 77 | vertices.shape = NPTS, 3 78 | 79 | 80 | def create_object(shader): 81 | # Create a new VAO (Vertex Array Object) and bind it 82 | vertex_array_object = GL.glGenVertexArrays(1) 83 | GL.glBindVertexArray(vertex_array_object) 84 | # Generate buffers to hold our vertices 85 | vertex_buffer = GL.glGenBuffers(1) 86 | GL.glBindBuffer(GL.GL_ARRAY_BUFFER, vertex_buffer) 87 | # Get the position of the 'position' in parameter of our shader 88 | # and bind it. 89 | position = GL.glGetAttribLocation(shader, bytestr('position')) 90 | GL.glEnableVertexAttribArray(position) 91 | # Describe the position data layout in the buffer 92 | GL.glVertexAttribPointer(position, 3, GL.GL_FLOAT, False, 93 | 0, ctypes.c_void_p(0)) 94 | # Send the data over to the buffer (bytes) 95 | vs = vertices.tostring() 96 | GL.glBufferData(GL.GL_ARRAY_BUFFER, len(vs), vs, GL.GL_STATIC_DRAW) 97 | # Unbind the VAO first (Important) 98 | GL.glBindVertexArray(0) 99 | # Unbind other stuff 100 | GL.glDisableVertexAttribArray(position) 101 | GL.glBindBuffer(GL.GL_ARRAY_BUFFER, 0) 102 | return vertex_array_object 103 | 104 | 105 | def rot(a, b, c): 106 | s = numpy.sin(a) 107 | c = numpy.cos(a) 108 | am = numpy.array(((c, s, 0), (-s, c, 0), (0, 0, 1)), numpy.float32) 109 | s = numpy.sin(b) 110 | c = numpy.cos(b) 111 | bm = numpy.array(((c, 0, s), (0, 1, 0), (-s, 0, c)), numpy.float32) 112 | s = numpy.sin(c) 113 | c = numpy.cos(c) 114 | cm = numpy.array(((1, 0, 0), (0, c, s), (0, -s, c)), numpy.float32) 115 | return numpy.dot(numpy.dot(am, bm), cm) 116 | 117 | 118 | class ShaderFrame(pyopengltk.OpenGLFrame): 119 | 120 | def initgl(self): 121 | # GLUT.glutInit(sys.argv) 122 | GL.glClearColor(0.15, 0.15, 0.15, 1.0) 123 | GL.glEnable(GL.GL_DEPTH_TEST) 124 | GL.glEnable(GL.GL_PROGRAM_POINT_SIZE) 125 | if not hasattr(self, "shader"): 126 | self.shader = OpenGL.GL.shaders.compileProgram( 127 | compileShader(vertex_shader, GL.GL_VERTEX_SHADER), 128 | compileShader(fragment_shader, GL.GL_FRAGMENT_SHADER) 129 | ) 130 | self.vertex_array_object = create_object(self.shader) 131 | self.proj = GL.glGetUniformLocation(self.shader, bytestr('proj')) 132 | self.nframes = 0 133 | self.start = time.time() 134 | 135 | def redraw(self): 136 | GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) 137 | GL.glUseProgram(self.shader) 138 | t = time.time()-self.start 139 | s = 2. 140 | p = rot(t*s/5., t*s/6., t*s/7.) 141 | GL.glUniformMatrix3fv(self.proj, 1, GL.GL_FALSE, p) 142 | GL.glBindVertexArray(self.vertex_array_object) 143 | GL.glDrawArrays(GL.GL_POINTS, 0, NPTS) 144 | GL.glBindVertexArray(0) 145 | GL.glUseProgram(0) 146 | GL.glRasterPos2f(-0.99, -0.99) 147 | if self.nframes > 1: 148 | t = time.time()-self.start 149 | fps = "fps: %5.2f frames: %d" % (self.nframes / t, self.nframes) 150 | # for c in fps: 151 | # GLUT.glutBitmapCharacter(GLUT.GLUT_BITMAP_HELVETICA_18, ord(c)); 152 | self.nframes += 1 153 | 154 | 155 | def main(): 156 | root = tk.Tk() 157 | app = ShaderFrame(root, width=512, height=512) 158 | app.pack(fill=tk.BOTH, expand=tk.YES) 159 | app.after(100, app.printContext) 160 | app.animate = 1000 // 60 161 | app.animate = 1 162 | app.mainloop() 163 | 164 | 165 | if __name__ == '__main__': 166 | main() 167 | -------------------------------------------------------------------------------- /pyopengltk/linux.py: -------------------------------------------------------------------------------- 1 | """ 2 | Linux implementation of the opengl frame 3 | """ 4 | from __future__ import print_function 5 | import logging 6 | from ctypes import c_int, c_char_p, c_void_p, cdll, POINTER, util, \ 7 | pointer, CFUNCTYPE, c_bool 8 | from OpenGL import GL, GLX 9 | from pyopengltk.base import BaseOpenGLFrame 10 | 11 | try: 12 | from OpenGL.raw._GLX import Display 13 | except: 14 | from OpenGL.raw.GLX._types import Display 15 | 16 | _log = logging.getLogger(__name__) 17 | 18 | 19 | _x11lib = cdll.LoadLibrary(util.find_library("X11")) 20 | XOpenDisplay = _x11lib.XOpenDisplay 21 | XOpenDisplay.argtypes = [c_char_p] 22 | XOpenDisplay.restype = POINTER(Display) 23 | 24 | Colormap = c_void_p 25 | # Attributes for old style creation 26 | att = [ 27 | GLX.GLX_RGBA, GLX.GLX_DOUBLEBUFFER, 28 | GLX.GLX_RED_SIZE, 4, 29 | GLX.GLX_GREEN_SIZE, 4, 30 | GLX.GLX_BLUE_SIZE, 4, 31 | GLX.GLX_DEPTH_SIZE, 16, 32 | 0, 33 | ] 34 | # Attributes for newer style creations 35 | fbatt = [ 36 | GLX.GLX_X_RENDERABLE, 1, 37 | GLX.GLX_DRAWABLE_TYPE, GLX.GLX_WINDOW_BIT, 38 | GLX.GLX_RENDER_TYPE, GLX.GLX_RGBA_BIT, 39 | GLX.GLX_RED_SIZE, 1, 40 | GLX.GLX_GREEN_SIZE, 1, 41 | GLX.GLX_BLUE_SIZE, 1, 42 | GLX.GLX_DOUBLEBUFFER, 1, 43 | 0, 44 | ] 45 | 46 | 47 | # Inherits the base and fills in the 3 platform dependent functions 48 | class OpenGLFrame(BaseOpenGLFrame): 49 | 50 | def __init__(self, *args, **kw): 51 | super().__init__(*args, **kw) 52 | 53 | def tkCreateContext(self): 54 | self.__window = XOpenDisplay(self.winfo_screen().encode('utf-8')) 55 | # Check glx version: 56 | major = c_int(0) 57 | minor = c_int(0) 58 | GLX.glXQueryVersion(self.__window, major, minor) 59 | print("GLX version: %d.%d" % (major.value, minor.value)) 60 | if major.value == 1 and minor.value < 3: # e.g. 1.2 and down 61 | visual = GLX.glXChooseVisual(self.__window, 0, (GL.GLint * len(att))(* att)) 62 | if not visual: 63 | _log.error("glXChooseVisual call failed") 64 | self.__context = GLX.glXCreateContext(self.__window, 65 | visual, 66 | None, 67 | GL.GL_TRUE) 68 | GLX.glXMakeCurrent(self.__window, self._wid, self.__context) 69 | return # OUT HERE FOR 1.2 and less 70 | else: 71 | # 1.3 or higher 72 | # which screen - should it be winfo_screen instead ?? 73 | XDefaultScreen = _x11lib.XDefaultScreen 74 | XDefaultScreen.argtypes = [POINTER(Display)] 75 | XDefaultScreen.restype = c_int 76 | screen = XDefaultScreen(self.__window) 77 | print("Screen is ", screen) 78 | # Look at framebuffer configs 79 | ncfg = GL.GLint(0) 80 | cfgs = GLX.glXChooseFBConfig( 81 | self.__window, 82 | screen, 83 | (GL.GLint * len(fbatt))(* fbatt), 84 | ncfg, 85 | ) 86 | print("Number of FBconfigs", ncfg.value) 87 | # 88 | # Try to match to the current window 89 | # ... might also be possible to set this for the frame 90 | # ... but for now we just take what Tk gave us 91 | ideal = int(self.winfo_visualid(), 16) # convert from hex 92 | best = -1 93 | for i in range(ncfg.value): 94 | vis = GLX.glXGetVisualFromFBConfig(self.__window, cfgs[i]) 95 | if ideal == vis.contents.visualid: 96 | best = i 97 | print("Got a matching visual: index %d %d xid %s" % ( 98 | best, vis.contents.visualid, hex(ideal))) 99 | if best < 0: 100 | print("oh dear - visual does not match") 101 | # Take the first in the list (should be another I guess) 102 | best = 0 103 | # Here we insist on RGBA - but didn't check earlier 104 | self.__context = GLX.glXCreateNewContext( 105 | self.__window, 106 | cfgs[best], 107 | GLX.GLX_RGBA_TYPE, 108 | None, # share list 109 | GL.GL_TRUE, # direct 110 | ) 111 | print("Is Direct?: ", GLX.glXIsDirect(self.__window, self.__context)) 112 | # Not creating another window ... some tutorials do 113 | # print("wid: ", self._wid) 114 | # self._wid = GLX.glXCreateWindow(self.__window, cfgs[best], self._wid, None) 115 | # print("wid: ", self._wid) 116 | GLX.glXMakeContextCurrent(self.__window, self._wid, self._wid, self.__context) 117 | print("Done making a first context") 118 | extensions = GLX.glXQueryExtensionsString(self.__window, screen) 119 | # Here we quit - getting a modern context needs further work below 120 | return 121 | if "GLX_ARB_create_context" in extensions: 122 | # We can try to upgrade it ?? 123 | print("Trying to upgrade context") 124 | s = "glXCreateContextAttribsARB" 125 | p = GLX.glXGetProcAddress(c_char_p(s)) 126 | 127 | print(p) 128 | if not p: 129 | p = GLX.glXGetProcAddressARB((GL.GLubyte * len(s)).from_buffer_copy(s)) 130 | print(p) 131 | if p: 132 | print(" p is true") 133 | p.restype = GLX.GLXContext 134 | p.argtypes = [ 135 | POINTER(Display), 136 | GLX.GLXFBConfig, 137 | GLX.GLXContext, 138 | c_bool, 139 | POINTER(c_int), 140 | ] 141 | arb_attrs = fbatt[:-1] + [] 142 | 143 | # GLX.GLX_CONTEXT_MAJOR_VERSION_ARB , 3 144 | # GLX.GLX_CONTEXT_MINOR_VERSION_ARB , 1, 145 | # 0 ] 146 | # 147 | # GLX.GLX_CONTEXT_FLAGS_ARB 148 | # GLX.GLX_CONTEXT_PROFILE_MASK_ARB 149 | # ] 150 | # import pdb 151 | # pdb.set_trace() 152 | self.__context = p( 153 | self.__window, cfgs[best], None, GL.GL_TRUE, 154 | (GL.GLint * len(arb_attrs))(* arb_attrs), 155 | ) 156 | 157 | def tkMakeCurrent(self): 158 | if self.winfo_ismapped(): 159 | GLX.glXMakeCurrent(self.__window, self._wid, self.__context) 160 | 161 | def tkSwapBuffers(self): 162 | if self.winfo_ismapped(): 163 | GLX.glXSwapBuffers(self.__window, self._wid) 164 | -------------------------------------------------------------------------------- /pyopengltk/opengl.py: -------------------------------------------------------------------------------- 1 | """ 2 | Code copied from pyopengl/Tk/__init__.py for compatibility 3 | Modified so it does not import * 4 | 5 | A class that creates an opengl widget. 6 | Mike Hartshorn 7 | Department of Chemistry 8 | University of York, UK 9 | """ 10 | import sys 11 | import math 12 | from OpenGL import GL, GLU 13 | from pyopengltk import OpenGLFrame 14 | 15 | if sys.version_info[0] < 3: 16 | import Tkinter as tk 17 | import Dialog as dialog 18 | else: 19 | import tkinter as tk 20 | from tkinter import dialog as dialog 21 | 22 | 23 | def glTranslateScene(s, x, y, mousex, mousey): 24 | GL.glMatrixMode(GL.GL_MODELVIEW) 25 | mat = GL.glGetDoublev(GL.GL_MODELVIEW_MATRIX) 26 | GL.glLoadIdentity() 27 | GL.glTranslatef(s * (x - mousex), s * (mousey - y), 0.0) 28 | GL.glMultMatrixd(mat) 29 | 30 | 31 | def glRotateScene(s, xcenter, ycenter, zcenter, x, y, mousex, mousey): 32 | GL.glMatrixMode(GL.GL_MODELVIEW) 33 | mat = GL.glGetDoublev(GL.GL_MODELVIEW_MATRIX) 34 | GL.glLoadIdentity() 35 | GL.glTranslatef(xcenter, ycenter, zcenter) 36 | GL.glRotatef(s * (y - mousey), 1., 0., 0.) 37 | GL.glRotatef(s * (x - mousex), 0., 1., 0.) 38 | GL.glTranslatef(-xcenter, -ycenter, -zcenter) 39 | GL.glMultMatrixd(mat) 40 | 41 | 42 | def v3distsq(a, b): 43 | d = (a[0] - b[0], a[1] - b[1], a[2] - b[2]) 44 | return d[0] * d[0] + d[1] * d[1] + d[2] * d[2] 45 | 46 | 47 | class RawOpengl(OpenGLFrame): 48 | """Widget without any sophisticated bindings\ 49 | by Tom Schwaller""" 50 | def __init__(self, master=None, cnf={}, **kw): 51 | OpenGLFrame.__init__(*(self, master, cnf), **kw) 52 | 53 | # replaces our _display method 54 | def tkRedraw(self, *dummy): 55 | # This must be outside of a pushmatrix, since a resize event 56 | # will call redraw recursively. 57 | self.update_idletasks() 58 | self.tkMakeCurrent() 59 | _mode = GL.glGetDoublev(GL.GL_MATRIX_MODE) 60 | try: 61 | GL.glMatrixMode(GL.GL_PROJECTION) 62 | GL.glPushMatrix() 63 | try: 64 | self.redraw() 65 | GL.glFlush() 66 | finally: 67 | GL.glPopMatrix() 68 | finally: 69 | GL.glMatrixMode(_mode) 70 | self.tkSwapBuffers() 71 | 72 | 73 | class Opengl(RawOpengl): 74 | """ 75 | Tkinter bindings for an Opengl widget. 76 | Mike Hartshorn 77 | Department of Chemistry 78 | University of York, UK 79 | http://www.yorvic.york.ac.uk/~mjh/ 80 | """ 81 | 82 | def __init__(self, master=None, cnf={}, **kw): 83 | """\ 84 | Create an opengl widget. 85 | Arrange for redraws when the window is exposed or when 86 | it changes size.""" 87 | 88 | # Widget.__init__(self, master, 'togl', cnf, kw) 89 | RawOpengl.__init__(*(self, master, cnf), **kw) 90 | self.initialised = 0 91 | 92 | # Current coordinates of the mouse. 93 | self.xmouse = 0 94 | self.ymouse = 0 95 | 96 | # Where we are centering. 97 | self.xcenter = 0.0 98 | self.ycenter = 0.0 99 | self.zcenter = 0.0 100 | 101 | # The _back color 102 | self.r_back = 1. 103 | self.g_back = 0. 104 | self.b_back = 1. 105 | 106 | # Where the eye is 107 | self.distance = 10.0 108 | 109 | # Field of view in y direction 110 | self.fovy = 30.0 111 | 112 | # Position of clipping planes. 113 | self.near = 0.1 114 | self.far = 1000.0 115 | 116 | # Is the widget allowed to autospin? 117 | self.autospin_allowed = 0 118 | 119 | # Is the widget currently autospinning? 120 | self.autospin = 0 121 | 122 | # Basic bindings for the virtual trackball 123 | self.bind('', self.tkHandlePick) 124 | # self.bind('', self.tkHandlePick) 125 | self.bind('', self.tkRecordMouse) 126 | self.bind('', self.tkTranslate) 127 | self.bind('', self.StartRotate) 128 | self.bind('', self.tkRotate) 129 | self.bind('', self.tkAutoSpin) 130 | self.bind('', self.tkRecordMouse) 131 | self.bind('', self.tkScale) 132 | 133 | def help(self): 134 | """Help for the widget.""" 135 | 136 | d = dialog.Dialog(None, { 137 | 'title': 'Viewer help', 138 | 'text': 'Button-1: Translate\n' 139 | 'Button-2: Rotate\n' 140 | 'Button-3: Zoom\n' 141 | 'Reset: Resets transformation to identity\n', 142 | 'bitmap': 'questhead', 143 | 'default': 0, 144 | 'strings': ('Done', 'Ok')}) 145 | assert d 146 | 147 | def activate(self): 148 | """Cause this Opengl widget to be the current destination for drawing.""" 149 | self.tkMakeCurrent() 150 | 151 | # This should almost certainly be part of some derived class. 152 | # But I have put it here for convenience. 153 | def basic_lighting(self): 154 | """\ 155 | Set up some basic lighting (single infinite light source). 156 | 157 | Also switch on the depth buffer.""" 158 | 159 | self.activate() 160 | light_position = (1, 1, 1, 0) 161 | GL.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, light_position) 162 | GL.glEnable(GL.GL_LIGHTING) 163 | GL.glEnable(GL.GL_LIGHT0) 164 | GL.glDepthFunc(GL.GL_LESS) 165 | GL.glEnable(GL.GL_DEPTH_TEST) 166 | GL.glMatrixMode(GL.GL_MODELVIEW) 167 | GL.glLoadIdentity() 168 | 169 | def initgl(self): 170 | self.basic_lighting() 171 | 172 | def set_background(self, r, g, b): 173 | """Change the background colour of the widget.""" 174 | 175 | self.r_back = r 176 | self.g_back = g 177 | self.b_back = b 178 | 179 | self.tkRedraw() 180 | 181 | def set_centerpoint(self, x, y, z): 182 | """Set the new center point for the model. 183 | This is where we are looking.""" 184 | 185 | self.xcenter = x 186 | self.ycenter = y 187 | self.zcenter = z 188 | 189 | self.tkRedraw() 190 | 191 | def set_eyepoint(self, distance): 192 | """Set how far the eye is from the position we are looking.""" 193 | 194 | self.distance = distance 195 | self.tkRedraw() 196 | 197 | def reset(self): 198 | """Reset rotation matrix for this widget.""" 199 | 200 | GL.glMatrixMode(GL.GL_MODELVIEW) 201 | GL.glLoadIdentity() 202 | self.tkRedraw() 203 | 204 | def tkHandlePick(self, event): 205 | """Handle a pick on the scene.""" 206 | 207 | if hasattr(self, 'pick'): 208 | # here we need to use glu.UnProject 209 | 210 | # Tk and X have their origin top left, 211 | # while Opengl has its origin bottom left. 212 | # So we need to subtract y from the window height to get 213 | # the proper pick position for Opengl 214 | 215 | realy = self.winfo_height() - event.y 216 | 217 | p1 = GLU.gluUnProject(event.x, realy, 0.) 218 | p2 = GLU.gluUnProject(event.x, realy, 1.) 219 | 220 | if self.pick(self, p1, p2): 221 | """If the pick method returns true we redraw the scene.""" 222 | 223 | self.tkRedraw() 224 | 225 | def tkRecordMouse(self, event): 226 | """Record the current mouse position.""" 227 | 228 | self.xmouse = event.x 229 | self.ymouse = event.y 230 | 231 | def StartRotate(self, event): 232 | # Switch off any autospinning if it was happening 233 | 234 | self.autospin = 0 235 | self.tkRecordMouse(event) 236 | 237 | def tkScale(self, event): 238 | """Scale the scene. Achieved by moving the eye position. 239 | 240 | Dragging up zooms in, while dragging down zooms out 241 | """ 242 | scale = 1 - 0.01 * (event.y - self.ymouse) 243 | # do some sanity checks, scale no more than 244 | # 1:1000 on any given click+drag 245 | if scale < 0.001: 246 | scale = 0.001 247 | elif scale > 1000: 248 | scale = 1000 249 | self.distance = self.distance * scale 250 | self.tkRedraw() 251 | self.tkRecordMouse(event) 252 | 253 | def do_AutoSpin(self): 254 | self.activate() 255 | 256 | glRotateScene(0.5, self.xcenter, self.ycenter, self.zcenter, 257 | self.yspin, self.xspin, 0, 0) 258 | self.tkRedraw() 259 | 260 | if self.autospin: 261 | self.after(10, self.do_AutoSpin) 262 | 263 | def tkAutoSpin(self, event): 264 | """Perform autospin of scene.""" 265 | 266 | self.after(4) 267 | self.update_idletasks() 268 | 269 | # This could be done with one call to pointerxy but I'm not sure 270 | # it would any quicker as we would have to split up the resulting 271 | # string and then conv 272 | 273 | x = self.tk.getint(self.tk.call('winfo', 'pointerx', self._w)) 274 | y = self.tk.getint(self.tk.call('winfo', 'pointery', self._w)) 275 | 276 | if self.autospin_allowed: 277 | if x != event.x_root and y != event.y_root: 278 | self.autospin = 1 279 | 280 | self.yspin = x - event.x_root 281 | self.xspin = y - event.y_root 282 | 283 | self.after(10, self.do_AutoSpin) 284 | 285 | def tkRotate(self, event): 286 | """Perform rotation of scene.""" 287 | 288 | self.activate() 289 | glRotateScene(0.5, self.xcenter, self.ycenter, self.zcenter, 290 | event.x, event.y, self.xmouse, self.ymouse) 291 | self.tkRedraw() 292 | self.tkRecordMouse(event) 293 | 294 | def tkTranslate(self, event): 295 | """Perform translation of scene.""" 296 | 297 | self.activate() 298 | 299 | # Scale mouse translations to object viewplane so object 300 | # tracks with mouse 301 | 302 | win_height = max(1, self.winfo_height()) 303 | obj_c = (self.xcenter, self.ycenter, self.zcenter) 304 | win = GLU.gluProject(obj_c[0], obj_c[1], obj_c[2]) 305 | obj = GLU.gluUnProject(win[0], win[1] + 0.5 * win_height, win[2]) 306 | dist = math.sqrt(v3distsq(obj, obj_c)) 307 | scale = abs(dist / (0.5 * win_height)) 308 | 309 | glTranslateScene(scale, event.x, event.y, self.xmouse, self.ymouse) 310 | self.tkRedraw() 311 | self.tkRecordMouse(event) 312 | 313 | def tkRedraw(self, *dummy): 314 | """Cause the opengl widget to redraw itself.""" 315 | 316 | if not self.initialised: 317 | return 318 | 319 | self.activate() 320 | 321 | GL.glPushMatrix() # Protect our matrix 322 | self.update_idletasks() 323 | self.activate() 324 | w = self.winfo_width() 325 | h = self.winfo_height() 326 | GL.glViewport(0, 0, w, h) 327 | 328 | # Clear the background and depth buffer. 329 | GL.glClearColor(self.r_back, self.g_back, self.b_back, 0.) 330 | GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) 331 | 332 | GL.glMatrixMode(GL.GL_PROJECTION) 333 | GL.glLoadIdentity() 334 | GLU.gluPerspective(self.fovy, float(w)/float(h), self.near, self.far) 335 | 336 | if 0: 337 | # Now translate the scene origin away from the world origin 338 | GL.glMatrixMode(GL.GL_MODELVIEW) 339 | mat = GL.glGetDoublev(GL.GL_MODELVIEW_MATRIX) 340 | GL.glLoadIdentity() 341 | GL.glTranslatef(-self.xcenter, -self.ycenter, -( 342 | self.zcenter+self.distance)) 343 | GL.glMultMatrixd(mat) 344 | else: 345 | GLU.gluLookAt(self.xcenter, self.ycenter, self.zcenter + self.distance, 346 | self.xcenter, self.ycenter, self.zcenter, 347 | 0., 1., 0.) 348 | GL.glMatrixMode(GL.GL_MODELVIEW) 349 | 350 | # Call objects redraw method. 351 | self.redraw(self) 352 | GL.glFlush() # Tidy up 353 | GL.glPopMatrix() # Restore the matrix 354 | 355 | self.tkSwapBuffers() 356 | 357 | def redraw(self, *args, **named): 358 | """Prevent access errors if user doesn't set redraw fast enough""" 359 | 360 | def tkExpose(self, *dummy): 361 | """Redraw the widget. 362 | Make it active, update tk events, call redraw procedure and 363 | swap the buffers. Note: swapbuffers is clever enough to 364 | only swap double buffered visuals.""" 365 | 366 | self.activate() 367 | if not self.initialised: 368 | self.basic_lighting() 369 | self.initialised = 1 370 | self.tkRedraw() 371 | 372 | def tkPrint(self, file): 373 | """Turn the current scene into PostScript via the feedback buffer.""" 374 | self.activate() 375 | --------------------------------------------------------------------------------