chiark / gitweb /
Merge branch 'master' into SteamEngine
[cura.git] / Cura / gui / sceneView.py
1 from __future__ import absolute_import
2
3 import wx
4 import numpy
5 import time
6 import os
7 import traceback
8 import shutil
9
10 import OpenGL
11 OpenGL.ERROR_CHECKING = False
12 from OpenGL.GLU import *
13 from OpenGL.GL import *
14
15 from Cura.gui import printWindow
16 from Cura.util import profile
17 from Cura.util import meshLoader
18 from Cura.util import objectScene
19 from Cura.util import resources
20 from Cura.util import sliceEngine
21 from Cura.util import machineCom
22 from Cura.util import removableStorage
23 from Cura.gui.util import opengl
24 from Cura.gui.util import openglGui
25
26 class anim(object):
27         def __init__(self, start, end, runTime):
28                 self._start = start
29                 self._end = end
30                 self._startTime = time.time()
31                 self._runTime = runTime
32
33         def isDone(self):
34                 return time.time() > self._startTime + self._runTime
35
36         def getPosition(self):
37                 if self.isDone():
38                         return self._end
39                 f = (time.time() - self._startTime) / self._runTime
40                 ts = f*f
41                 tc = f*f*f
42                 #f = 6*tc*ts + -15*ts*ts + 10*tc
43                 f = tc + -3*ts + 3*f
44                 return self._start + (self._end - self._start) * f
45
46 class SceneView(openglGui.glGuiPanel):
47         def __init__(self, parent):
48                 super(SceneView, self).__init__(parent)
49
50                 self._yaw = 30
51                 self._pitch = 60
52                 self._zoom = 300
53                 self._scene = objectScene.Scene()
54                 self._objectShader = None
55                 self._focusObj = None
56                 self._selectedObj = None
57                 self._objColors = [None,None,None,None]
58                 self._mouseX = -1
59                 self._mouseY = -1
60                 self._mouseState = None
61                 self._viewTarget = numpy.array([0,0,0], numpy.float32)
62                 self._animView = None
63                 self._animZoom = None
64                 self._platformMesh = meshLoader.loadMeshes(resources.getPathForMesh('ultimaker_platform.stl'))[0]
65                 self._platformMesh._drawOffset = numpy.array([0,0,0.5], numpy.float32)
66                 self._isSimpleMode = True
67
68                 self.openFileButton      = openglGui.glButton(self, 4, 'Load', (0,0), self.ShowLoadModel)
69                 self.printButton         = openglGui.glButton(self, 6, 'Print', (1,0), self.ShowPrintWindow)
70                 self.printButton.setDisabled(True)
71
72                 self._slicer = sliceEngine.Slicer(self._updateSliceProgress)
73                 self._sceneUpdateTimer = wx.Timer(self)
74                 self.Bind(wx.EVT_TIMER, lambda e : self._slicer.runSlicer(self._scene), self._sceneUpdateTimer)
75                 self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
76
77                 self.updateProfileToControls()
78                 wx.EVT_IDLE(self, self.OnIdle)
79
80         def ShowLoadModel(self, button):
81                 if button == 1:
82                         dlg=wx.FileDialog(self, 'Open 3D model', os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
83                         dlg.SetWildcard(meshLoader.wildcardFilter())
84                         if dlg.ShowModal() != wx.ID_OK:
85                                 dlg.Destroy()
86                                 return
87                         filename = dlg.GetPath()
88                         dlg.Destroy()
89                         if not(os.path.exists(filename)):
90                                 return False
91                         profile.putPreference('lastFile', filename)
92                         self.GetParent().GetParent().GetParent().addToModelMRU(filename)
93                         self.loadScene([filename])
94
95         def ShowPrintWindow(self, button):
96                 if button == 1:
97                         if machineCom.machineIsConnected():
98                                 printWindow.printFile(self._slicer.getGCodeFilename())
99                         elif len(removableStorage.getPossibleSDcardDrives()) > 0:
100                                 drives = removableStorage.getPossibleSDcardDrives()
101                                 if len(drives) > 1:
102                                         pass
103                         else:
104                                 defPath = profile.getPreference('lastFile')
105                                 defPath = defPath[0:defPath.rfind('.')] + '.gcode'
106                                 dlg=wx.FileDialog(self, 'Save toolpath', defPath, style=wx.FD_SAVE)
107                                 dlg.SetFilename(defPath)
108                                 dlg.SetWildcard('Toolpath (*.gcode)|*.gcode;*.g')
109                                 if dlg.ShowModal() != wx.ID_OK:
110                                         dlg.Destroy()
111                                         return
112                                 filename = dlg.GetPath()
113                                 dlg.Destroy()
114
115                                 shutil.copy(self._slicer.getGCodeFilename(), filename)
116
117         def OnIdle(self, e):
118                 if self._animView is not None or self._animZoom is not None:
119                         self.Refresh()
120                         return
121                 for obj in self._scene.objects():
122                         if obj._loadAnim is not None:
123                                 self.Refresh()
124                                 return
125
126         def sceneUpdated(self):
127                 self._sceneUpdateTimer.Start(1, True)
128                 self._slicer.abortSlicer()
129                 self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
130                 self.Refresh()
131
132         def _updateSliceProgress(self, progressValue, ready):
133                 self.printButton.setDisabled(not ready)
134                 self.printButton.setProgressBar(progressValue)
135                 self.Refresh()
136
137         def loadScene(self, fileList):
138                 for filename in fileList:
139                         try:
140                                 objList = meshLoader.loadMeshes(filename)
141                         except:
142                                 traceback.print_exc()
143                         else:
144                                 for obj in objList:
145                                         obj._loadAnim = anim(1, 0, 1.5)
146                                         self._scene.add(obj)
147                                         self._scene.centerAll()
148                                         self._selectObject(obj)
149                 self.sceneUpdated()
150
151         def _deleteObject(self, obj):
152                 if obj == self._selectedObj:
153                         self._selectedObj = None
154                 if obj == self._focusObj:
155                         self._focusObj = None
156                 self._scene.remove(obj)
157                 for m in obj._meshList:
158                         if m.vbo is not None:
159                                 self.glReleaseList.append(m.vbo)
160                 if self._isSimpleMode:
161                         self._scene.arrangeAll()
162                 self.sceneUpdated()
163
164         def _selectObject(self, obj, zoom = True):
165                 if obj != self._selectedObj:
166                         self._selectedObj = obj
167                 if zoom:
168                         newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getMaximum()[2] / 2])
169                         self._animView = anim(self._viewTarget.copy(), newViewPos, 0.5)
170                         newZoom = obj.getBoundaryCircle() * 6
171                         if newZoom > numpy.max(self._machineSize) * 3:
172                                 newZoom = numpy.max(self._machineSize) * 3
173                         self._animZoom = anim(self._zoom, newZoom, 0.5)
174
175         def updateProfileToControls(self):
176                 oldSimpleMode = self._isSimpleMode
177                 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
178                 if self._isSimpleMode and not oldSimpleMode:
179                         self._scene.arrangeAll()
180                         self.sceneUpdated()
181                 self._machineSize = numpy.array([profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')])
182                 self._objColors[0] = profile.getPreferenceColour('model_colour')
183                 self._objColors[1] = profile.getPreferenceColour('model_colour2')
184                 self._objColors[2] = profile.getPreferenceColour('model_colour3')
185                 self._objColors[3] = profile.getPreferenceColour('model_colour4')
186                 self._scene.setMachineSize(self._machineSize)
187
188         def OnKeyChar(self, keyCode):
189                 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE:
190                         if self._selectedObj is not None:
191                                 self._deleteObject(self._selectedObj)
192                                 self.Refresh()
193
194                 if keyCode == wx.WXK_F3:
195                         shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
196
197         def ShaderUpdate(self, v, f):
198                 s = opengl.GLShader(v, f)
199                 if s.isValid():
200                         self._objectLoadShader.release()
201                         self._objectLoadShader = s
202                         for obj in self._scene.objects():
203                                 obj._loadAnim = anim(1, 0, 1.5)
204                         self.Refresh()
205
206         def OnMouseDown(self,e):
207                 self._mouseX = e.GetX()
208                 self._mouseY = e.GetY()
209                 self._mouseClick3DPos = self._mouse3Dpos
210                 self._mouseClickFocus = self._focusObj
211                 if e.ButtonDClick():
212                         self._mouseState = 'doubleClick'
213                 else:
214                         self._mouseState = 'dragOrClick'
215                 if self._mouseState == 'dragOrClick':
216                         if e.Button == 1:
217                                 if self._focusObj is not None:
218                                         self._selectObject(self._focusObj, False)
219                                         self.Refresh()
220
221         def OnMouseUp(self, e):
222                 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
223                         return
224                 if self._mouseState == 'dragOrClick':
225                         if e.Button == 1:
226                                 if self._focusObj is not None:
227                                         self._selectObject(self._focusObj)
228                                 else:
229                                         self._selectedObj = None
230                                         self.Refresh()
231                         if e.Button == 3 and self._selectedObj == self._focusObj:
232                                 #menu = wx.Menu()
233                                 #menu.Append(-1, 'Test')
234                                 #self.PopupMenu(menu)
235                                 #menu.Destroy()
236                                 pass
237                 if self._mouseState == 'dragObject' and self._selectedObj is not None:
238                         self._scene.pushFree()
239                         self.sceneUpdated()
240                 self._mouseState = None
241
242         def OnMouseMotion(self,e):
243                 if e.Dragging() and self._mouseState is not None:
244                         self._mouseState = 'drag'
245                         if not e.LeftIsDown() and e.RightIsDown():
246                                 self._yaw += e.GetX() - self._mouseX
247                                 self._pitch -= e.GetY() - self._mouseY
248                                 if self._pitch > 170:
249                                         self._pitch = 170
250                                 if self._pitch < 10:
251                                         self._pitch = 10
252                         elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
253                                 self._zoom += e.GetY() - self._mouseY
254                                 if self._zoom < 1:
255                                         self._zoom = 1
256                                 if self._zoom > numpy.max(self._machineSize) * 3:
257                                         self._zoom = numpy.max(self._machineSize) * 3
258                         elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus and not self._isSimpleMode:
259                                 self._mouseState = 'dragObject'
260                                 z = max(0, self._mouseClick3DPos[2])
261                                 p0 = opengl.unproject(self._mouseX, self.viewport[1] + self.viewport[3] - self._mouseY, 0, self.modelMatrix, self.projMatrix, self.viewport)
262                                 p1 = opengl.unproject(self._mouseX, self.viewport[1] + self.viewport[3] - self._mouseY, 1, self.modelMatrix, self.projMatrix, self.viewport)
263                                 p2 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport)
264                                 p3 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport)
265                                 p0[2] -= z
266                                 p1[2] -= z
267                                 p2[2] -= z
268                                 p3[2] -= z
269                                 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
270                                 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
271                                 diff = cursorZ1 - cursorZ0
272                                 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
273
274                 self._mouseX = e.GetX()
275                 self._mouseY = e.GetY()
276
277         def OnMouseWheel(self, e):
278                 self._zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
279                 if self._zoom < 1.0:
280                         self._zoom = 1.0
281                 if self._zoom > numpy.max(self._machineSize) * 3:
282                         self._zoom = numpy.max(self._machineSize) * 3
283                 self.Refresh()
284
285         def _init3DView(self):
286                 # set viewing projection
287                 size = self.GetSize()
288                 glViewport(0, 0, size.GetWidth(), size.GetHeight())
289                 glLoadIdentity()
290
291                 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
292
293                 glDisable(GL_RESCALE_NORMAL)
294                 glDisable(GL_LIGHTING)
295                 glDisable(GL_LIGHT0)
296                 glEnable(GL_DEPTH_TEST)
297                 glDisable(GL_CULL_FACE)
298                 glDisable(GL_BLEND)
299                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
300
301                 glClearColor(0.8, 0.8, 0.8, 1.0)
302                 glClearStencil(0)
303                 glClearDepth(1.0)
304
305                 glMatrixMode(GL_PROJECTION)
306                 glLoadIdentity()
307                 aspect = float(size.GetWidth()) / float(size.GetHeight())
308                 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
309
310                 glMatrixMode(GL_MODELVIEW)
311                 glLoadIdentity()
312                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
313
314         def OnPaint(self,e):
315                 if machineCom.machineIsConnected():
316                         self.printButton._imageID = 6
317                         self.printButton._tooltip = 'Print'
318                 elif len(removableStorage.getPossibleSDcardDrives()) > 0:
319                         self.printButton._imageID = 2
320                         self.printButton._tooltip = 'Toolpath to SD'
321                 else:
322                         self.printButton._imageID = 3
323                         self.printButton._tooltip = 'Save toolpath'
324
325                 if self._animView is not None:
326                         self._viewTarget = self._animView.getPosition()
327                         if self._animView.isDone():
328                                 self._animView = None
329                 if self._animZoom is not None:
330                         self._zoom = self._animZoom.getPosition()
331                         if self._animZoom.isDone():
332                                 self._animZoom = None
333                 if self._objectShader is None:
334                         self._objectShader = opengl.GLShader("""
335 uniform float cameraDistance;
336 varying float light_amount;
337
338 void main(void)
339 {
340     gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
341     gl_FrontColor = gl_Color;
342
343         light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
344         light_amount *= 1 - (length(gl_Position.xyz - vec3(0,0,cameraDistance)) / 1.5 / cameraDistance);
345         light_amount += 0.2;
346 }
347                         ""","""
348 uniform float cameraDistance;
349 varying float light_amount;
350
351 void main(void)
352 {
353         gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
354 }
355                         """)
356                         self._objectLoadShader = opengl.GLShader("""
357 uniform float cameraDistance;
358 uniform float intensity;
359 uniform float scale;
360 varying float light_amount;
361
362 void main(void)
363 {
364         vec4 tmp = gl_Vertex;
365     tmp.x += sin(tmp.z/5+intensity*30) * scale * intensity;
366     tmp.y += sin(tmp.z/3+intensity*40) * scale * intensity;
367     gl_Position = gl_ModelViewProjectionMatrix * tmp;
368     gl_FrontColor = gl_Color;
369
370         light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
371         light_amount *= 1 - (length(gl_Position.xyz - vec3(0,0,cameraDistance)) / 1.5 / cameraDistance);
372         light_amount += 0.2;
373 }
374                         ""","""
375 uniform float cameraDistance;
376 uniform float intensity;
377 varying float light_amount;
378
379 void main(void)
380 {
381         gl_FragColor = vec4(gl_Color.xyz * light_amount, 1-intensity);
382 }
383                         """)
384                 self._init3DView()
385                 glTranslate(0,0,-self._zoom)
386                 glRotate(-self._pitch, 1,0,0)
387                 glRotate(self._yaw, 0,0,1)
388                 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
389
390                 self.viewport = glGetIntegerv(GL_VIEWPORT)
391                 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
392                 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
393
394                 glClearColor(1,1,1,1)
395                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
396
397                 for n in xrange(0, len(self._scene.objects())):
398                         obj = self._scene.objects()[n]
399                         glColor4ub((n >> 24) & 0xFF, (n >> 16) & 0xFF, (n >> 8) & 0xFF, n & 0xFF)
400                         self._renderObject(obj)
401
402                 if self._mouseX > -1:
403                         n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0]
404                         if n < len(self._scene.objects()):
405                                 self._focusObj = self._scene.objects()[n]
406                         else:
407                                 self._focusObj = None
408                         f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
409                         self._mouse3Dpos = opengl.unproject(self._mouseX, self.viewport[1] + self.viewport[3] - self._mouseY, f, self.modelMatrix, self.projMatrix, self.viewport)
410
411                 self._init3DView()
412                 glTranslate(0,0,-self._zoom)
413                 glRotate(-self._pitch, 1,0,0)
414                 glRotate(self._yaw, 0,0,1)
415                 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
416
417                 glStencilFunc(GL_ALWAYS, 1, 1)
418                 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
419                 self._objectShader.bind()
420                 self._objectShader.setUniform('cameraDistance', self._zoom)
421                 for obj in self._scene.objects():
422                         if obj._loadAnim is not None:
423                                 if obj._loadAnim.isDone():
424                                         obj._loadAnim = None
425                                 else:
426                                         continue
427                         col = self._objColors[0]
428                         if not self._scene.checkPlatform(obj):
429                                 col = [0.5,0.5,0.5,0.8]
430                         glDisable(GL_STENCIL_TEST)
431                         if self._selectedObj == obj:
432                                 col = map(lambda n: n * 1.5, col)
433                                 glEnable(GL_STENCIL_TEST)
434                         elif self._focusObj == obj:
435                                 col = map(lambda n: n * 1.2, col)
436                         elif self._focusObj is not None or  self._selectedObj is not None:
437                                 col = map(lambda n: n * 0.8, col)
438                         glColor4f(col[0], col[1], col[2], col[3])
439                         self._renderObject(obj)
440                 self._objectShader.unbind()
441
442                 glDisable(GL_STENCIL_TEST)
443                 glEnable(GL_BLEND)
444                 self._objectLoadShader.bind()
445                 self._objectLoadShader.setUniform('cameraDistance', self._zoom)
446                 glColor4f(0.2, 0.6, 1.0, 1.0)
447                 for obj in self._scene.objects():
448                         if obj._loadAnim is None:
449                                 continue
450                         self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
451                         self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
452                         self._renderObject(obj)
453                 self._objectLoadShader.unbind()
454                 glDisable(GL_BLEND)
455
456                 self._drawMachine()
457
458                 #Draw the outline of the selected object, on top of everything else except the GUI.
459                 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
460                         glDisable(GL_DEPTH_TEST)
461                         glEnable(GL_CULL_FACE)
462                         glEnable(GL_STENCIL_TEST)
463                         glStencilFunc(GL_EQUAL, 0, 255)
464                         glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
465                         glLineWidth(2)
466                         glColor4f(1,1,1,0.5)
467                         self._renderObject(self._selectedObj)
468                         glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
469                         glDisable(GL_STENCIL_TEST)
470                         glDisable(GL_CULL_FACE)
471                         glEnable(GL_DEPTH_TEST)
472
473         def _renderObject(self, obj):
474                 glPushMatrix()
475                 glTranslate(obj.getPosition()[0], obj.getPosition()[1], 0)
476                 offset = obj.getDrawOffset()
477                 glTranslate(-offset[0], -offset[1], -offset[2])
478                 for m in obj._meshList:
479                         if m.vbo is None:
480                                 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
481                         m.vbo.render()
482                 glPopMatrix()
483
484         def _drawMachine(self):
485                 glEnable(GL_CULL_FACE)
486                 glEnable(GL_BLEND)
487
488                 if profile.getPreference('machine_type') == 'ultimaker':
489                         glColor4f(1,1,1,0.5)
490                         self._objectShader.bind()
491                         self._objectShader.setUniform('cameraDistance', self._zoom)
492                         self._renderObject(self._platformMesh)
493                         self._objectShader.unbind()
494
495                 size = [profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')]
496                 v0 = [ size[0] / 2, size[1] / 2, size[2]]
497                 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
498                 v2 = [-size[0] / 2, size[1] / 2, size[2]]
499                 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
500                 v4 = [ size[0] / 2, size[1] / 2, 0]
501                 v5 = [ size[0] / 2,-size[1] / 2, 0]
502                 v6 = [-size[0] / 2, size[1] / 2, 0]
503                 v7 = [-size[0] / 2,-size[1] / 2, 0]
504
505                 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
506                 glEnableClientState(GL_VERTEX_ARRAY)
507                 glVertexPointer(3, GL_FLOAT, 3*4, vList)
508
509                 glColor4ub(5, 171, 231, 64)
510                 glDrawArrays(GL_QUADS, 0, 4)
511                 glColor4ub(5, 171, 231, 96)
512                 glDrawArrays(GL_QUADS, 4, 8)
513                 glColor4ub(5, 171, 231, 128)
514                 glDrawArrays(GL_QUADS, 12, 8)
515
516                 sx = self._machineSize[0]
517                 sy = self._machineSize[1]
518                 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
519                         for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
520                                 x1 = x * 10
521                                 x2 = x1 + 10
522                                 y1 = y * 10
523                                 y2 = y1 + 10
524                                 x1 = max(min(x1, sx/2), -sx/2)
525                                 y1 = max(min(y1, sy/2), -sy/2)
526                                 x2 = max(min(x2, sx/2), -sx/2)
527                                 y2 = max(min(y2, sy/2), -sy/2)
528                                 if (x & 1) == (y & 1):
529                                         glColor4ub(5, 171, 231, 127)
530                                 else:
531                                         glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
532                                 glBegin(GL_QUADS)
533                                 glVertex3f(x1, y1, -0.02)
534                                 glVertex3f(x2, y1, -0.02)
535                                 glVertex3f(x2, y2, -0.02)
536                                 glVertex3f(x1, y2, -0.02)
537                                 glEnd()
538
539                 glDisableClientState(GL_VERTEX_ARRAY)
540                 glDisable(GL_BLEND)
541                 glDisable(GL_CULL_FACE)
542
543 class shaderEditor(wx.Dialog):
544         def __init__(self, parent, callback, v, f):
545                 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
546                 self._callback = callback
547                 s = wx.BoxSizer(wx.VERTICAL)
548                 self.SetSizer(s)
549                 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
550                 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
551                 s.Add(self._vertex, 1, flag=wx.EXPAND)
552                 s.Add(self._fragment, 1, flag=wx.EXPAND)
553
554                 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
555                 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
556
557                 self.SetPosition(self.GetParent().GetPosition())
558                 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
559                 self.Show()
560
561         def OnText(self, e):
562                 self._callback(self._vertex.GetValue(), self._fragment.GetValue())