printrun-src/printrun/gl/panel.py

changeset 46
cce0af6351f0
parent 15
0bbb006204fc
equal deleted inserted replaced
45:c82943fb205f 46:cce0af6351f0
1 #!/usr/bin/env python
2
3 # This file is part of the Printrun suite. 1 # This file is part of the Printrun suite.
4 # 2 #
5 # Printrun is free software: you can redistribute it and/or modify 3 # Printrun is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by 4 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or 5 # the Free Software Foundation, either version 3 of the License, or
37 GLdouble, glGetDoublev, glGetIntegerv, GLint, \ 35 GLdouble, glGetDoublev, glGetIntegerv, GLint, \
38 GL_LEQUAL, glLoadIdentity, glMatrixMode, GL_MODELVIEW, \ 36 GL_LEQUAL, glLoadIdentity, glMatrixMode, GL_MODELVIEW, \
39 GL_MODELVIEW_MATRIX, GL_ONE_MINUS_SRC_ALPHA, glOrtho, \ 37 GL_MODELVIEW_MATRIX, GL_ONE_MINUS_SRC_ALPHA, glOrtho, \
40 GL_PROJECTION, GL_PROJECTION_MATRIX, glScalef, \ 38 GL_PROJECTION, GL_PROJECTION_MATRIX, glScalef, \
41 GL_SRC_ALPHA, glTranslatef, gluPerspective, gluUnProject, \ 39 GL_SRC_ALPHA, glTranslatef, gluPerspective, gluUnProject, \
42 glViewport, GL_VIEWPORT 40 glViewport, GL_VIEWPORT, glPushMatrix, glPopMatrix, \
41 glBegin, glVertex2f, glVertex3f, glEnd, GL_LINE_LOOP, glColor3f, \
42 GL_LINE_STIPPLE, glColor4f, glLineStipple
43
43 from pyglet import gl 44 from pyglet import gl
44 from .trackball import trackball, mulquat 45 from .trackball import trackball, mulquat, axis_to_quat
45 from .libtatlin.actors import vec 46 from .libtatlin.actors import vec
46 47 from pyglet.gl.glu import gluOrtho2D
47 class wxGLPanel(wx.Panel): 48
49 # When Subclassing wx.Window in Windows the focus goes to the wx.Window
50 # instead of GLCanvas and it does not draw the focus rectangle and
51 # does not consume used keystrokes
52 # BASE_CLASS = wx.Window
53 # Subclassing Panel solves problem In Windows
54 BASE_CLASS = wx.Panel
55 # BASE_CLASS = wx.ScrolledWindow
56 # BASE_CLASS = glcanvas.GLCanvas
57 class wxGLPanel(BASE_CLASS):
48 '''A simple class for using OpenGL with wxPython.''' 58 '''A simple class for using OpenGL with wxPython.'''
49 59
60 orbit_control = True
50 orthographic = True 61 orthographic = True
51 color_background = (0.98, 0.98, 0.78, 1) 62 color_background = (0.98, 0.98, 0.78, 1)
52 do_lights = True 63 do_lights = True
53 64
54 def __init__(self, parent, id, pos = wx.DefaultPosition, 65 def __init__(self, parent, pos = wx.DefaultPosition,
55 size = wx.DefaultSize, style = 0, 66 size = wx.DefaultSize, style = 0,
56 antialias_samples = 0): 67 antialias_samples = 0):
57 # Forcing a no full repaint to stop flickering 68 # Full repaint should not be a performance problem
58 style = style | wx.NO_FULL_REPAINT_ON_RESIZE 69 #TODO: test on windows, tested in Ubuntu
59 super(wxGLPanel, self).__init__(parent, id, pos, size, style) 70 style = style | wx.FULL_REPAINT_ON_RESIZE
60 71
61 self.GLinitialized = False 72 self.GLinitialized = False
62 self.mview_initialized = False 73 self.mview_initialized = False
63 attribList = (glcanvas.WX_GL_RGBA, # RGBA 74 attribList = [glcanvas.WX_GL_RGBA, # RGBA
64 glcanvas.WX_GL_DOUBLEBUFFER, # Double Buffered 75 glcanvas.WX_GL_DOUBLEBUFFER, # Double Buffered
65 glcanvas.WX_GL_DEPTH_SIZE, 24) # 24 bit 76 glcanvas.WX_GL_DEPTH_SIZE, 24 # 24 bit
77 ]
66 78
67 if antialias_samples > 0 and hasattr(glcanvas, "WX_GL_SAMPLE_BUFFERS"): 79 if antialias_samples > 0 and hasattr(glcanvas, "WX_GL_SAMPLE_BUFFERS"):
68 attribList += (glcanvas.WX_GL_SAMPLE_BUFFERS, 1, 80 attribList += (glcanvas.WX_GL_SAMPLE_BUFFERS, 1,
69 glcanvas.WX_GL_SAMPLES, antialias_samples) 81 glcanvas.WX_GL_SAMPLES, antialias_samples)
70 82
71 self.width = None 83 attribList.append(0)
72 self.height = None 84
73 85 if BASE_CLASS is glcanvas.GLCanvas:
74 self.sizer = wx.BoxSizer(wx.HORIZONTAL) 86 super().__init__(parent, wx.ID_ANY, attribList, pos, size, style)
75 self.canvas = glcanvas.GLCanvas(self, attribList = attribList) 87 self.canvas = self
88 else:
89 super().__init__(parent, wx.ID_ANY, pos, size, style)
90 self.canvas = glcanvas.GLCanvas(self, wx.ID_ANY, attribList, pos, size, style)
91
92 self.width = self.height = None
93
76 self.context = glcanvas.GLContext(self.canvas) 94 self.context = glcanvas.GLContext(self.canvas)
77 self.sizer.Add(self.canvas, 1, wx.EXPAND)
78 self.SetSizerAndFit(self.sizer)
79 95
80 self.rot_lock = Lock() 96 self.rot_lock = Lock()
81 self.basequat = [0, 0, 0, 1] 97 self.basequat = [0, 0, 0, 1]
82 self.zoom_factor = 1.0 98 self.zoom_factor = 1.0
99 self.angle_z = 0
100 self.angle_x = 0
83 101
84 self.gl_broken = False 102 self.gl_broken = False
85 103
86 # bind events 104 # bind events
105 self.canvas.Bind(wx.EVT_SIZE, self.processSizeEvent)
106 if self.canvas is not self:
107 self.Bind(wx.EVT_SIZE, self.OnScrollSize)
108 # do not focus parent (panel like) but its canvas
109 self.SetCanFocus(False)
110
87 self.canvas.Bind(wx.EVT_ERASE_BACKGROUND, self.processEraseBackgroundEvent) 111 self.canvas.Bind(wx.EVT_ERASE_BACKGROUND, self.processEraseBackgroundEvent)
88 self.canvas.Bind(wx.EVT_SIZE, self.processSizeEvent) 112 # In wxWidgets 3.0.x there is a clipping bug during resizing
113 # which could be affected by painting the container
114 # self.Bind(wx.EVT_PAINT, self.processPaintEvent)
115 # Upgrade to wxPython 4.1 recommended
89 self.canvas.Bind(wx.EVT_PAINT, self.processPaintEvent) 116 self.canvas.Bind(wx.EVT_PAINT, self.processPaintEvent)
117
118 self.canvas.Bind(wx.EVT_SET_FOCUS, self.processFocus)
119 self.canvas.Bind(wx.EVT_KILL_FOCUS, self.processKillFocus)
120
121 def processFocus(self, ev):
122 # print('processFocus')
123 self.Refresh(False)
124 ev.Skip()
125
126 def processKillFocus(self, ev):
127 # print('processKillFocus')
128 self.Refresh(False)
129 ev.Skip()
130 # def processIdle(self, event):
131 # print('processIdle')
132 # event.Skip()
133
134 def Layout(self):
135 return super().Layout()
136
137 def Refresh(self, eraseback=True):
138 # print('Refresh')
139 return super().Refresh(eraseback)
140
141 def OnScrollSize(self, event):
142 self.canvas.SetSize(event.Size)
90 143
91 def processEraseBackgroundEvent(self, event): 144 def processEraseBackgroundEvent(self, event):
92 '''Process the erase background event.''' 145 '''Process the erase background event.'''
93 pass # Do nothing, to avoid flashing on MSWin 146 pass # Do nothing, to avoid flashing on MSWin
94 147
95 def processSizeEvent(self, event): 148 def processSizeEvent(self, event):
96 '''Process the resize event.''' 149 '''Process the resize event.'''
97 if self.IsFrozen(): 150
98 event.Skip() 151 # print('processSizeEvent frozen', self.IsFrozen(), event.Size.x, self.ClientSize.x)
99 return 152 if not self.IsFrozen() and self.canvas.IsShownOnScreen():
100 if (wx.VERSION > (2, 9) and self.canvas.IsShownOnScreen()) or self.canvas.GetContext():
101 # Make sure the frame is shown before calling SetCurrent. 153 # Make sure the frame is shown before calling SetCurrent.
102 self.canvas.SetCurrent(self.context) 154 self.canvas.SetCurrent(self.context)
103 self.OnReshape() 155 self.OnReshape()
104 self.Refresh(False) 156
105 timer = wx.CallLater(100, self.Refresh) 157 # self.Refresh(False)
106 timer.Start() 158 # print('Refresh')
107 event.Skip() 159 event.Skip()
108 160
109 def processPaintEvent(self, event): 161 def processPaintEvent(self, event):
110 '''Process the drawing event.''' 162 '''Process the drawing event.'''
163 # print('wxGLPanel.processPaintEvent', self.ClientSize.Width)
111 self.canvas.SetCurrent(self.context) 164 self.canvas.SetCurrent(self.context)
112 165
113 if not self.gl_broken: 166 if not self.gl_broken:
114 try: 167 try:
115 self.OnInitGL() 168 self.OnInitGL()
116 self.OnDraw() 169 self.DrawCanvas()
117 except pyglet.gl.lib.GLException: 170 except pyglet.gl.lib.GLException:
118 self.gl_broken = True 171 self.gl_broken = True
119 logging.error(_("OpenGL failed, disabling it:") 172 logging.error(_("OpenGL failed, disabling it:")
120 + "\n" + traceback.format_exc()) 173 + "\n" + traceback.format_exc())
121 event.Skip() 174 event.Skip()
122 175
123 def Destroy(self): 176 def Destroy(self):
124 # clean up the pyglet OpenGL context 177 # clean up the pyglet OpenGL context
125 self.pygletcontext.destroy() 178 self.pygletcontext.destroy()
126 # call the super method 179 # call the super method
127 super(wxGLPanel, self).Destroy() 180 super().Destroy()
128 181
129 # ========================================================================== 182 # ==========================================================================
130 # GLFrame OpenGL Event Handlers 183 # GLFrame OpenGL Event Handlers
131 # ========================================================================== 184 # ==========================================================================
132 def OnInitGL(self, call_reshape = True): 185 def OnInitGL(self, call_reshape = True):
158 if width < 1 or height < 1: 211 if width < 1 or height < 1:
159 return 212 return
160 self.width = max(float(width), 1.0) 213 self.width = max(float(width), 1.0)
161 self.height = max(float(height), 1.0) 214 self.height = max(float(height), 1.0)
162 self.OnInitGL(call_reshape = False) 215 self.OnInitGL(call_reshape = False)
216 # print('glViewport', width)
163 glViewport(0, 0, width, height) 217 glViewport(0, 0, width, height)
164 glMatrixMode(GL_PROJECTION) 218 glMatrixMode(GL_PROJECTION)
165 glLoadIdentity() 219 glLoadIdentity()
166 if self.orthographic: 220 if self.orthographic:
167 glOrtho(-width / 2, width / 2, -height / 2, height / 2, 221 glOrtho(-width / 2, width / 2, -height / 2, height / 2,
221 self.zoom_factor = 1.0 275 self.zoom_factor = 1.0
222 self.zoomed_width = wratio / minratio 276 self.zoomed_width = wratio / minratio
223 self.zoomed_height = hratio / minratio 277 self.zoomed_height = hratio / minratio
224 glScalef(factor * minratio, factor * minratio, 1) 278 glScalef(factor * minratio, factor * minratio, 1)
225 279
226 def OnDraw(self, *args, **kwargs): 280 def DrawCanvas(self):
227 """Draw the window.""" 281 """Draw the window."""
282 #import time
283 #start = time.perf_counter()
284 # print('DrawCanvas', self.canvas.GetClientRect())
228 self.pygletcontext.set_current() 285 self.pygletcontext.set_current()
229 glClearColor(*self.color_background) 286 glClearColor(*self.color_background)
230 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) 287 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
231 self.draw_objects() 288 self.draw_objects()
289
290 if self.canvas.HasFocus():
291 self.drawFocus()
232 self.canvas.SwapBuffers() 292 self.canvas.SwapBuffers()
293 #print('Draw took', '%.2f'%(time.perf_counter()-start))
294
295 def drawFocus(self):
296 glColor4f(0, 0, 0, 0.4)
297
298 glPushMatrix()
299 glLoadIdentity()
300
301 glMatrixMode(GL_PROJECTION)
302 glPushMatrix()
303 glLoadIdentity()
304 gluOrtho2D(0, self.width, 0, self.height)
305
306 glLineStipple(1, 0xf0f0)
307 glEnable(GL_LINE_STIPPLE)
308 glBegin(GL_LINE_LOOP)
309 glVertex2f(1, 0)
310 glVertex2f(self.width, 0)
311 glVertex2f(self.width, self.height-1)
312 glVertex2f(1, self.height-1)
313 glEnd()
314 glDisable(GL_LINE_STIPPLE)
315
316 glPopMatrix() # restore PROJECTION
317
318 glMatrixMode(GL_MODELVIEW)
319 glPopMatrix()
233 320
234 # ========================================================================== 321 # ==========================================================================
235 # To be implemented by a sub class 322 # To be implemented by a sub class
236 # ========================================================================== 323 # ==========================================================================
237 def create_objects(self): 324 def create_objects(self):
315 glTranslatef(delta_x, delta_y, 0) 402 glTranslatef(delta_x, delta_y, 0)
316 glScalef(factor, factor, 1) 403 glScalef(factor, factor, 1)
317 self.zoom_factor *= factor 404 self.zoom_factor *= factor
318 if to: 405 if to:
319 glTranslatef(-delta_x, -delta_y, 0) 406 glTranslatef(-delta_x, -delta_y, 0)
320 wx.CallAfter(self.Refresh) 407 # For wxPython (<4.1) and GTK:
408 # when you resize (enlarge) 3d view fast towards the log pane
409 # sash garbage may remain in GLCanvas
410 # The following refresh clears it at the cost of
411 # doubled frame draws.
412 # wx.CallAfter(self.Refresh)
413 self.Refresh(False)
321 414
322 def zoom_to_center(self, factor): 415 def zoom_to_center(self, factor):
323 self.canvas.SetCurrent(self.context) 416 self.canvas.SetCurrent(self.context)
324 x, y, _ = self.mouse_to_3d(self.width / 2, self.height / 2) 417 x, y, _ = self.mouse_to_3d(self.width / 2, self.height / 2)
325 self.zoom(factor, (x, y)) 418 self.zoom(factor, (x, y))
326 419
420 def orbit(self, p1x, p1y, p2x, p2y):
421 rz = p2x-p1x
422 self.angle_z-=rz
423 rotz = axis_to_quat([0.0,0.0,1.0],self.angle_z)
424
425 rx = p2y-p1y
426 self.angle_x+=rx
427 rota = axis_to_quat([1.0,0.0,0.0],self.angle_x)
428 return mulquat(rotz,rota)
429
327 def handle_rotation(self, event): 430 def handle_rotation(self, event):
328 if self.initpos is None: 431 if self.initpos is None:
329 self.initpos = event.GetPositionTuple() 432 self.initpos = event.GetPosition()
330 else: 433 else:
331 p1 = self.initpos 434 p1 = self.initpos
332 p2 = event.GetPositionTuple() 435 p2 = event.GetPosition()
333 sz = self.GetClientSize() 436 sz = self.GetClientSize()
334 p1x = float(p1[0]) / (sz[0] / 2) - 1 437 p1x = p1[0] / (sz[0] / 2) - 1
335 p1y = 1 - float(p1[1]) / (sz[1] / 2) 438 p1y = 1 - p1[1] / (sz[1] / 2)
336 p2x = float(p2[0]) / (sz[0] / 2) - 1 439 p2x = p2[0] / (sz[0] / 2) - 1
337 p2y = 1 - float(p2[1]) / (sz[1] / 2) 440 p2y = 1 - p2[1] / (sz[1] / 2)
338 quat = trackball(p1x, p1y, p2x, p2y, self.dist / 250.0) 441 quat = trackball(p1x, p1y, p2x, p2y, self.dist / 250.0)
339 with self.rot_lock: 442 with self.rot_lock:
340 self.basequat = mulquat(self.basequat, quat) 443 if self.orbit_control:
444 self.basequat = self.orbit(p1x, p1y, p2x, p2y)
445 else:
446 self.basequat = mulquat(self.basequat, quat)
341 self.initpos = p2 447 self.initpos = p2
342 448
343 def handle_translation(self, event): 449 def handle_translation(self, event):
344 if self.initpos is None: 450 if self.initpos is None:
345 self.initpos = event.GetPositionTuple() 451 self.initpos = event.GetPosition()
346 else: 452 else:
347 p1 = self.initpos 453 p1 = self.initpos
348 p2 = event.GetPositionTuple() 454 p2 = event.GetPosition()
349 if self.orthographic: 455 if self.orthographic:
350 x1, y1, _ = self.mouse_to_3d(p1[0], p1[1]) 456 x1, y1, _ = self.mouse_to_3d(p1[0], p1[1])
351 x2, y2, _ = self.mouse_to_3d(p2[0], p2[1]) 457 x2, y2, _ = self.mouse_to_3d(p2[0], p2[1])
352 glTranslatef(x2 - x1, y2 - y1, 0) 458 glTranslatef(x2 - x1, y2 - y1, 0)
353 else: 459 else:

mercurial