chiark / gitweb /
Added comments
[cura.git] / Cura / gui / sceneView.py
1 from __future__ import absolute_import
2 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
3
4 import wx
5 import numpy
6 import time
7 import os
8 import traceback
9 import threading
10 import math
11 import platform
12
13 import OpenGL
14 OpenGL.ERROR_CHECKING = False
15 from OpenGL.GLU import *
16 from OpenGL.GL import *
17
18 from Cura.gui import printWindow
19 from Cura.util import profile
20 from Cura.util import meshLoader
21 from Cura.util import objectScene
22 from Cura.util import resources
23 from Cura.util import sliceEngine
24 from Cura.util import machineCom
25 from Cura.util import removableStorage
26 from Cura.util import gcodeInterpreter
27 from Cura.gui.util import previewTools
28 from Cura.gui.util import opengl
29 from Cura.gui.util import openglGui
30 from Cura.gui.tools import youmagineGui
31 from Cura.gui.tools import imageToMesh
32
33 class SceneView(openglGui.glGuiPanel):
34         def __init__(self, parent):
35                 super(SceneView, self).__init__(parent)
36
37                 self._yaw = 30
38                 self._pitch = 60
39                 self._zoom = 300
40                 self._scene = objectScene.Scene()
41                 self._gcode = None
42                 self._gcodeVBOs = []
43                 self._gcodeFilename = None
44                 self._gcodeLoadThread = None
45                 self._objectShader = None
46                 self._objectLoadShader = None
47                 self._focusObj = None
48                 self._selectedObj = None
49                 self._objColors = [None,None,None,None]
50                 self._mouseX = -1
51                 self._mouseY = -1
52                 self._mouseState = None
53                 self._viewTarget = numpy.array([0,0,0], numpy.float32)
54                 self._animView = None
55                 self._animZoom = None
56                 self._platformMesh = {}
57                 self._isSimpleMode = True
58                 self._usbPrintMonitor = printWindow.printProcessMonitor(lambda : self._queueRefresh())
59
60                 self._viewport = None
61                 self._modelMatrix = None
62                 self._projMatrix = None
63                 self.tempMatrix = None
64
65                 self.openFileButton      = openglGui.glButton(self, 4, _("Load"), (0,0), self.showLoadModel)
66                 self.printButton         = openglGui.glButton(self, 6, _("Print"), (1,0), self.OnPrintButton)
67                 self.printButton.setDisabled(True)
68
69                 group = []
70                 self.rotateToolButton = openglGui.glRadioButton(self, 8, _("Rotate"), (0,-1), group, self.OnToolSelect)
71                 self.scaleToolButton  = openglGui.glRadioButton(self, 9, _("Scale"), (1,-1), group, self.OnToolSelect)
72                 self.mirrorToolButton  = openglGui.glRadioButton(self, 10, _("Mirror"), (2,-1), group, self.OnToolSelect)
73
74                 self.resetRotationButton = openglGui.glButton(self, 12, _("Reset"), (0,-2), self.OnRotateReset)
75                 self.layFlatButton       = openglGui.glButton(self, 16, _("Lay flat"), (0,-3), self.OnLayFlat)
76
77                 self.resetScaleButton    = openglGui.glButton(self, 13, _("Reset"), (1,-2), self.OnScaleReset)
78                 self.scaleMaxButton      = openglGui.glButton(self, 17, _("To max"), (1,-3), self.OnScaleMax)
79
80                 self.mirrorXButton       = openglGui.glButton(self, 14, _("Mirror X"), (2,-2), lambda button: self.OnMirror(0))
81                 self.mirrorYButton       = openglGui.glButton(self, 18, _("Mirror Y"), (2,-3), lambda button: self.OnMirror(1))
82                 self.mirrorZButton       = openglGui.glButton(self, 22, _("Mirror Z"), (2,-4), lambda button: self.OnMirror(2))
83
84                 self.rotateToolButton.setExpandArrow(True)
85                 self.scaleToolButton.setExpandArrow(True)
86                 self.mirrorToolButton.setExpandArrow(True)
87
88                 self.scaleForm = openglGui.glFrame(self, (2, -2))
89                 openglGui.glGuiLayoutGrid(self.scaleForm)
90                 openglGui.glLabel(self.scaleForm, _("Scale X"), (0,0))
91                 self.scaleXctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,0), lambda value: self.OnScaleEntry(value, 0))
92                 openglGui.glLabel(self.scaleForm, _("Scale Y"), (0,1))
93                 self.scaleYctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,1), lambda value: self.OnScaleEntry(value, 1))
94                 openglGui.glLabel(self.scaleForm, _("Scale Z"), (0,2))
95                 self.scaleZctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,2), lambda value: self.OnScaleEntry(value, 2))
96                 openglGui.glLabel(self.scaleForm, _("Size X (mm)"), (0,4))
97                 self.scaleXmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,4), lambda value: self.OnScaleEntryMM(value, 0))
98                 openglGui.glLabel(self.scaleForm, _("Size Y (mm)"), (0,5))
99                 self.scaleYmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,5), lambda value: self.OnScaleEntryMM(value, 1))
100                 openglGui.glLabel(self.scaleForm, _("Size Z (mm)"), (0,6))
101                 self.scaleZmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,6), lambda value: self.OnScaleEntryMM(value, 2))
102                 openglGui.glLabel(self.scaleForm, _("Uniform scale"), (0,8))
103                 self.scaleUniform = openglGui.glCheckbox(self.scaleForm, True, (1,8), None)
104
105                 self.viewSelection = openglGui.glComboButton(self, _("View mode"), [7,19,11,15,23], [_("Normal"), _("Overhang"), _("Transparent"), _("X-Ray"), _("Layers")], (-1,0), self.OnViewChange)
106                 self.layerSelect = openglGui.glSlider(self, 10000, 0, 1, (-1,-2), lambda : self.QueueRefresh())
107
108                 self.youMagineButton = openglGui.glButton(self, 26, _("Share on YouMagine"), (2,0), lambda button: youmagineGui.youmagineManager(self.GetTopLevelParent(), self._scene))
109                 self.youMagineButton.setDisabled(True)
110
111                 self.notification = openglGui.glNotification(self, (0, 0))
112
113                 self._slicer = sliceEngine.Slicer(self._updateSliceProgress)
114                 self._sceneUpdateTimer = wx.Timer(self)
115                 self.Bind(wx.EVT_TIMER, self._onRunSlicer, self._sceneUpdateTimer)
116                 self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
117                 self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave)
118
119                 self.OnViewChange()
120                 self.OnToolSelect(0)
121                 self.updateToolButtons()
122                 self.updateProfileToControls()
123
124         def loadGCodeFile(self, filename):
125                 self.OnDeleteAll(None)
126                 if self._gcode is not None:
127                         self._gcode = None
128                         for layerVBOlist in self._gcodeVBOs:
129                                 for vbo in layerVBOlist:
130                                         self.glReleaseList.append(vbo)
131                         self._gcodeVBOs = []
132                 self._gcode = gcodeInterpreter.gcode()
133                 self._gcodeFilename = filename
134                 self.printButton.setBottomText('')
135                 self.viewSelection.setValue(4)
136                 self.printButton.setDisabled(False)
137                 self.youMagineButton.setDisabled(True)
138                 self.OnViewChange()
139
140         def loadSceneFiles(self, filenames):
141                 self.youMagineButton.setDisabled(False)
142                 #if self.viewSelection.getValue() == 4:
143                 #       self.viewSelection.setValue(0)
144                 #       self.OnViewChange()
145                 self.loadScene(filenames)
146
147         def loadFiles(self, filenames):
148                 mainWindow = self.GetParent().GetParent().GetParent()
149                 # only one GCODE file can be active
150                 # so if single gcode file, process this
151                 # otherwise ignore all gcode files
152                 gcodeFilename = None
153                 if len(filenames) == 1:
154                         filename = filenames[0]
155                         ext = os.path.splitext(filename)[1].lower()
156                         if ext == '.g' or ext == '.gcode':
157                                 gcodeFilename = filename
158                                 mainWindow.addToModelMRU(filename)
159                 if gcodeFilename is not None:
160                         self.loadGCodeFile(gcodeFilename)
161                 else:
162                         # process directories and special file types
163                         # and keep scene files for later processing
164                         scene_filenames = []
165                         ignored_types = dict()
166                         # use file list as queue
167                         # pop first entry for processing and append new files at end
168                         while filenames:
169                                 filename = filenames.pop(0)
170                                 if os.path.isdir(filename):
171                                         # directory: queue all included files and directories
172                                         filenames.extend(os.path.join(filename, f) for f in os.listdir(filename))
173                                 else:
174                                         ext = os.path.splitext(filename)[1].lower()
175                                         if ext == '.ini':
176                                                 profile.loadProfile(filename)
177                                                 mainWindow.addToProfileMRU(filename)
178                                         elif ext in meshLoader.loadSupportedExtensions() or ext in imageToMesh.supportedExtensions():
179                                                 scene_filenames.append(filename)
180                                                 mainWindow.addToModelMRU(filename)
181                                         else:
182                                                 ignored_types[ext] = 1
183                         if ignored_types:
184                                 ignored_types = ignored_types.keys()
185                                 ignored_types.sort()
186                                 self.notification.message("ignored: " + " ".join("*" + type for type in ignored_types))
187                         mainWindow.updateProfileToAllControls()
188                         # now process all the scene files
189                         if scene_filenames:
190                                 self.loadSceneFiles(scene_filenames)
191                                 self._selectObject(None)
192                                 self.sceneUpdated()
193                                 newZoom = numpy.max(self._machineSize)
194                                 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
195                                 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
196
197         def showLoadModel(self, button = 1):
198                 if button == 1:
199                         dlg=wx.FileDialog(self, _("Open 3D model"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST|wx.FD_MULTIPLE)
200                         dlg.SetWildcard(meshLoader.loadWildcardFilter() + imageToMesh.wildcardList() + "|GCode file (*.gcode)|*.g;*.gcode;*.G;*.GCODE")
201                         if dlg.ShowModal() != wx.ID_OK:
202                                 dlg.Destroy()
203                                 return
204                         filenames = dlg.GetPaths()
205                         dlg.Destroy()
206                         if len(filenames) < 1:
207                                 return False
208                         profile.putPreference('lastFile', filenames[0])
209                         self.loadFiles(filenames)
210
211         def showSaveModel(self):
212                 if len(self._scene.objects()) < 1:
213                         return
214                 dlg=wx.FileDialog(self, _("Save 3D model"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
215                 dlg.SetWildcard(meshLoader.saveWildcardFilter())
216                 if dlg.ShowModal() != wx.ID_OK:
217                         dlg.Destroy()
218                         return
219                 filename = dlg.GetPath()
220                 dlg.Destroy()
221                 meshLoader.saveMeshes(filename, self._scene.objects())
222
223         def OnPrintButton(self, button):
224                 if button == 1:
225                         if machineCom.machineIsConnected():
226                                 self.showPrintWindow()
227                         elif len(removableStorage.getPossibleSDcardDrives()) > 0:
228                                 drives = removableStorage.getPossibleSDcardDrives()
229                                 if len(drives) > 1:
230                                         dlg = wx.SingleChoiceDialog(self, "Select SD drive", "Multiple removable drives have been found,\nplease select your SD card drive", map(lambda n: n[0], drives))
231                                         if dlg.ShowModal() != wx.ID_OK:
232                                                 dlg.Destroy()
233                                                 return
234                                         drive = drives[dlg.GetSelection()]
235                                         dlg.Destroy()
236                                 else:
237                                         drive = drives[0]
238                                 filename = self._scene._objectList[0].getName() + '.gcode'
239                                 threading.Thread(target=self._copyFile,args=(self._gcodeFilename, drive[1] + filename, drive[1])).start()
240                         else:
241                                 self.showSaveGCode()
242                 if button == 3:
243                         menu = wx.Menu()
244                         self.Bind(wx.EVT_MENU, lambda e: self.showPrintWindow(), menu.Append(-1, _("Print with USB")))
245                         self.Bind(wx.EVT_MENU, lambda e: self.showSaveGCode(), menu.Append(-1, _("Save GCode...")))
246                         self.Bind(wx.EVT_MENU, lambda e: self._showSliceLog(), menu.Append(-1, _("Slice engine log...")))
247                         self.PopupMenu(menu)
248                         menu.Destroy()
249
250         def showPrintWindow(self):
251                 if self._gcodeFilename is None:
252                         return
253                 self._usbPrintMonitor.loadFile(self._gcodeFilename, self._slicer.getID())
254                 if self._gcodeFilename == self._slicer.getGCodeFilename():
255                         self._slicer.submitSliceInfoOnline()
256
257         def showSaveGCode(self):
258                 if len(self._scene._objectList) < 1:
259                         return
260                 dlg=wx.FileDialog(self, _("Save toolpath"), os.path.dirname(profile.getPreference('lastFile')), style=wx.FD_SAVE)
261                 filename = self._scene._objectList[0].getName() + '.gcode'
262                 dlg.SetFilename(filename)
263                 dlg.SetWildcard('Toolpath (*.gcode)|*.gcode;*.g')
264                 if dlg.ShowModal() != wx.ID_OK:
265                         dlg.Destroy()
266                         return
267                 filename = dlg.GetPath()
268                 dlg.Destroy()
269
270                 threading.Thread(target=self._copyFile,args=(self._gcodeFilename, filename)).start()
271
272         def _copyFile(self, fileA, fileB, allowEject = False):
273                 try:
274                         size = float(os.stat(fileA).st_size)
275                         with open(fileA, 'rb') as fsrc:
276                                 with open(fileB, 'wb') as fdst:
277                                         while 1:
278                                                 buf = fsrc.read(16*1024)
279                                                 if not buf:
280                                                         break
281                                                 fdst.write(buf)
282                                                 self.printButton.setProgressBar(float(fsrc.tell()) / size)
283                                                 self._queueRefresh()
284                 except:
285                         import sys
286                         print sys.exc_info()
287                         self.notification.message("Failed to save")
288                 else:
289                         if allowEject:
290                                 self.notification.message("Saved as %s" % (fileB), lambda : self.notification.message('You can now eject the card.') if removableStorage.ejectDrive(allowEject) else self.notification.message('Safe remove failed...'))
291                         else:
292                                 self.notification.message("Saved as %s" % (fileB))
293                 self.printButton.setProgressBar(None)
294                 if fileA == self._slicer.getGCodeFilename():
295                         self._slicer.submitSliceInfoOnline()
296
297         def _showSliceLog(self):
298                 dlg = wx.TextEntryDialog(self, _("The slicing engine reported the following"), _("Engine log..."), '\n'.join(self._slicer.getSliceLog()), wx.TE_MULTILINE | wx.OK | wx.CENTRE)
299                 dlg.ShowModal()
300                 dlg.Destroy()
301
302         def OnToolSelect(self, button):
303                 if self.rotateToolButton.getSelected():
304                         self.tool = previewTools.toolRotate(self)
305                 elif self.scaleToolButton.getSelected():
306                         self.tool = previewTools.toolScale(self)
307                 elif self.mirrorToolButton.getSelected():
308                         self.tool = previewTools.toolNone(self)
309                 else:
310                         self.tool = previewTools.toolNone(self)
311                 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
312                 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
313                 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
314                 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
315                 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
316                 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
317                 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
318                 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
319
320         def updateToolButtons(self):
321                 if self._selectedObj is None:
322                         hidden = True
323                 else:
324                         hidden = False
325                 self.rotateToolButton.setHidden(hidden)
326                 self.scaleToolButton.setHidden(hidden)
327                 self.mirrorToolButton.setHidden(hidden)
328                 if hidden:
329                         self.rotateToolButton.setSelected(False)
330                         self.scaleToolButton.setSelected(False)
331                         self.mirrorToolButton.setSelected(False)
332                         self.OnToolSelect(0)
333
334         def OnViewChange(self):
335                 if self.viewSelection.getValue() == 4:
336                         self.viewMode = 'gcode'
337                         if self._gcode is not None and self._gcode.layerList is not None:
338                                 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
339                         self._selectObject(None)
340                 elif self.viewSelection.getValue() == 1:
341                         self.viewMode = 'overhang'
342                 elif self.viewSelection.getValue() == 2:
343                         self.viewMode = 'transparent'
344                 elif self.viewSelection.getValue() == 3:
345                         self.viewMode = 'xray'
346                 else:
347                         self.viewMode = 'normal'
348                 self.layerSelect.setHidden(self.viewMode != 'gcode')
349                 self.QueueRefresh()
350
351         def OnRotateReset(self, button):
352                 if self._selectedObj is None:
353                         return
354                 self._selectedObj.resetRotation()
355                 self._scene.pushFree()
356                 self._selectObject(self._selectedObj)
357                 self.sceneUpdated()
358
359         def OnLayFlat(self, button):
360                 if self._selectedObj is None:
361                         return
362                 self._selectedObj.layFlat()
363                 self._scene.pushFree()
364                 self._selectObject(self._selectedObj)
365                 self.sceneUpdated()
366
367         def OnScaleReset(self, button):
368                 if self._selectedObj is None:
369                         return
370                 self._selectedObj.resetScale()
371                 self._selectObject(self._selectedObj)
372                 self.updateProfileToControls()
373                 self.sceneUpdated()
374
375         def OnScaleMax(self, button):
376                 if self._selectedObj is None:
377                         return
378                 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([1,1,1], numpy.float32))
379                 self._scene.pushFree()
380                 self._selectObject(self._selectedObj)
381                 self.updateProfileToControls()
382                 self.sceneUpdated()
383
384         def OnMirror(self, axis):
385                 if self._selectedObj is None:
386                         return
387                 self._selectedObj.mirror(axis)
388                 self.sceneUpdated()
389
390         def OnScaleEntry(self, value, axis):
391                 if self._selectedObj is None:
392                         return
393                 try:
394                         value = float(value)
395                 except:
396                         return
397                 self._selectedObj.setScale(value, axis, self.scaleUniform.getValue())
398                 self.updateProfileToControls()
399                 self._scene.pushFree()
400                 self._selectObject(self._selectedObj)
401                 self.sceneUpdated()
402
403         def OnScaleEntryMM(self, value, axis):
404                 if self._selectedObj is None:
405                         return
406                 try:
407                         value = float(value)
408                 except:
409                         return
410                 self._selectedObj.setSize(value, axis, self.scaleUniform.getValue())
411                 self.updateProfileToControls()
412                 self._scene.pushFree()
413                 self._selectObject(self._selectedObj)
414                 self.sceneUpdated()
415
416         def OnDeleteAll(self, e):
417                 while len(self._scene.objects()) > 0:
418                         self._deleteObject(self._scene.objects()[0])
419                 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
420
421         def OnMultiply(self, e):
422                 if self._focusObj is None:
423                         return
424                 obj = self._focusObj
425                 dlg = wx.NumberEntryDialog(self, _("How many copies do you want?"), _("Number of copies"), _("Multiply"), 1, 1, 100)
426                 if dlg.ShowModal() != wx.ID_OK:
427                         dlg.Destroy()
428                         return
429                 cnt = dlg.GetValue()
430                 dlg.Destroy()
431                 n = 0
432                 while True:
433                         n += 1
434                         newObj = obj.copy()
435                         self._scene.add(newObj)
436                         self._scene.centerAll()
437                         if not self._scene.checkPlatform(newObj):
438                                 break
439                         if n > cnt:
440                                 break
441                 if n <= cnt:
442                         self.notification.message("Could not create more then %d items" % (n - 1))
443                 self._scene.remove(newObj)
444                 self._scene.centerAll()
445                 self.sceneUpdated()
446
447         def OnSplitObject(self, e):
448                 if self._focusObj is None:
449                         return
450                 self._scene.remove(self._focusObj)
451                 for obj in self._focusObj.split(self._splitCallback):
452                         if numpy.max(obj.getSize()) > 2.0:
453                                 self._scene.add(obj)
454                 self._scene.centerAll()
455                 self._selectObject(None)
456                 self.sceneUpdated()
457
458         def OnCenter(self, e):
459                 if self._focusObj is None:
460                         return
461                 self._focusObj.setPosition(numpy.array([0.0, 0.0]))
462                 self._scene.pushFree()
463                 newViewPos = numpy.array([self._focusObj.getPosition()[0], self._focusObj.getPosition()[1], self._focusObj.getSize()[2] / 2])
464                 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
465                 self.sceneUpdated()
466
467         def _splitCallback(self, progress):
468                 print progress
469
470         def OnMergeObjects(self, e):
471                 if self._selectedObj is None or self._focusObj is None or self._selectedObj == self._focusObj:
472                         if len(self._scene.objects()) == 2:
473                                 self._scene.merge(self._scene.objects()[0], self._scene.objects()[1])
474                                 self.sceneUpdated()
475                         return
476                 self._scene.merge(self._selectedObj, self._focusObj)
477                 self.sceneUpdated()
478
479         def sceneUpdated(self):
480                 self._sceneUpdateTimer.Start(500, True)
481                 self._slicer.abortSlicer()
482                 self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
483                 self.QueueRefresh()
484
485         def _onRunSlicer(self, e):
486                 if self._isSimpleMode:
487                         self.GetTopLevelParent().simpleSettingsPanel.setupSlice()
488                 self._slicer.runSlicer(self._scene)
489                 if self._isSimpleMode:
490                         profile.resetTempOverride()
491
492         def _updateSliceProgress(self, progressValue, ready):
493                 if not ready:
494                         if self.printButton.getProgressBar() is not None and progressValue >= 0.0 and abs(self.printButton.getProgressBar() - progressValue) < 0.01:
495                                 return
496                 self.printButton.setDisabled(not ready)
497                 if progressValue >= 0.0:
498                         self.printButton.setProgressBar(progressValue)
499                 else:
500                         self.printButton.setProgressBar(None)
501                 if self._gcode is not None:
502                         self._gcode = None
503                         for layerVBOlist in self._gcodeVBOs:
504                                 for vbo in layerVBOlist:
505                                         self.glReleaseList.append(vbo)
506                         self._gcodeVBOs = []
507                 if ready:
508                         self.printButton.setProgressBar(None)
509                         text = '%s' % (self._slicer.getPrintTime())
510                         for e in xrange(0, int(profile.getMachineSetting('extruder_amount'))):
511                                 amount = self._slicer.getFilamentAmount(e)
512                                 if amount is None:
513                                         continue
514                                 text += '\n%s' % (amount)
515                                 cost = self._slicer.getFilamentCost(e)
516                                 if cost is not None:
517                                         text += '\n%s' % (cost)
518                         self.printButton.setBottomText(text)
519                         self._gcode = gcodeInterpreter.gcode()
520                         self._gcodeFilename = self._slicer.getGCodeFilename()
521                 else:
522                         self.printButton.setBottomText('')
523                 self.QueueRefresh()
524
525         def _loadGCode(self):
526                 self._gcode.progressCallback = self._gcodeLoadCallback
527                 self._gcode.load(self._gcodeFilename)
528
529         def _gcodeLoadCallback(self, progress):
530                 if not self or self._gcode is None:
531                         return True
532                 if len(self._gcode.layerList) % 15 == 0:
533                         time.sleep(0.1)
534                 if self._gcode is None:
535                         return True
536                 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
537                 if self.viewMode == 'gcode':
538                         self._queueRefresh()
539                 return False
540
541         def loadScene(self, fileList):
542                 for filename in fileList:
543                         try:
544                                 ext = os.path.splitext(filename)[1].lower()
545                                 if ext in imageToMesh.supportedExtensions():
546                                         imageToMesh.convertImageDialog(self, filename).Show()
547                                         objList = []
548                                 else:
549                                         objList = meshLoader.loadMeshes(filename)
550                         except:
551                                 traceback.print_exc()
552                         else:
553                                 for obj in objList:
554                                         if self._objectLoadShader is not None:
555                                                 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
556                                         else:
557                                                 obj._loadAnim = None
558                                         self._scene.add(obj)
559                                         self._scene.centerAll()
560                                         self._selectObject(obj)
561                                         if obj.getScale()[0] < 1.0:
562                                                 self.notification.message("Warning: Object scaled down.")
563                 self.sceneUpdated()
564
565         def _deleteObject(self, obj):
566                 if obj == self._selectedObj:
567                         self._selectObject(None)
568                 if obj == self._focusObj:
569                         self._focusObj = None
570                 self._scene.remove(obj)
571                 for m in obj._meshList:
572                         if m.vbo is not None and m.vbo.decRef():
573                                 self.glReleaseList.append(m.vbo)
574                 import gc
575                 gc.collect()
576                 self.sceneUpdated()
577
578         def _selectObject(self, obj, zoom = True):
579                 if obj != self._selectedObj:
580                         self._selectedObj = obj
581                         self.updateProfileToControls()
582                         self.updateToolButtons()
583                 if zoom and obj is not None:
584                         newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2])
585                         self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
586                         newZoom = obj.getBoundaryCircle() * 6
587                         if newZoom > numpy.max(self._machineSize) * 3:
588                                 newZoom = numpy.max(self._machineSize) * 3
589                         self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
590
591         def updateProfileToControls(self):
592                 oldSimpleMode = self._isSimpleMode
593                 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
594                 if self._isSimpleMode != oldSimpleMode:
595                         self._scene.arrangeAll()
596                         self.sceneUpdated()
597                 self._machineSize = numpy.array([profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')])
598                 self._objColors[0] = profile.getPreferenceColour('model_colour')
599                 self._objColors[1] = profile.getPreferenceColour('model_colour2')
600                 self._objColors[2] = profile.getPreferenceColour('model_colour3')
601                 self._objColors[3] = profile.getPreferenceColour('model_colour4')
602                 self._scene.setMachineSize(self._machineSize)
603                 self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
604                 self._scene.setHeadSize(profile.getMachineSettingFloat('extruder_head_size_min_x'), profile.getMachineSettingFloat('extruder_head_size_max_x'), profile.getMachineSettingFloat('extruder_head_size_min_y'), profile.getMachineSettingFloat('extruder_head_size_max_y'), profile.getMachineSettingFloat('extruder_head_size_height'))
605
606                 if self._selectedObj is not None:
607                         scale = self._selectedObj.getScale()
608                         size = self._selectedObj.getSize()
609                         self.scaleXctrl.setValue(round(scale[0], 2))
610                         self.scaleYctrl.setValue(round(scale[1], 2))
611                         self.scaleZctrl.setValue(round(scale[2], 2))
612                         self.scaleXmmctrl.setValue(round(size[0], 2))
613                         self.scaleYmmctrl.setValue(round(size[1], 2))
614                         self.scaleZmmctrl.setValue(round(size[2], 2))
615
616         def OnKeyChar(self, keyCode):
617                 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE or (keyCode == wx.WXK_BACK and platform.system() == "Darwin"):
618                         if self._selectedObj is not None:
619                                 self._deleteObject(self._selectedObj)
620                                 self.QueueRefresh()
621                 if keyCode == wx.WXK_UP:
622                         self.layerSelect.setValue(self.layerSelect.getValue() + 1)
623                         self.QueueRefresh()
624                 elif keyCode == wx.WXK_DOWN:
625                         self.layerSelect.setValue(self.layerSelect.getValue() - 1)
626                         self.QueueRefresh()
627                 elif keyCode == wx.WXK_PAGEUP:
628                         self.layerSelect.setValue(self.layerSelect.getValue() + 10)
629                         self.QueueRefresh()
630                 elif keyCode == wx.WXK_PAGEDOWN:
631                         self.layerSelect.setValue(self.layerSelect.getValue() - 10)
632                         self.QueueRefresh()
633
634                 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
635                         shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
636                 if keyCode == wx.WXK_F4 and wx.GetKeyState(wx.WXK_SHIFT):
637                         from collections import defaultdict
638                         from gc import get_objects
639                         self._beforeLeakTest = defaultdict(int)
640                         for i in get_objects():
641                                 self._beforeLeakTest[type(i)] += 1
642                 if keyCode == wx.WXK_F5 and wx.GetKeyState(wx.WXK_SHIFT):
643                         from collections import defaultdict
644                         from gc import get_objects
645                         self._afterLeakTest = defaultdict(int)
646                         for i in get_objects():
647                                 self._afterLeakTest[type(i)] += 1
648                         for k in self._afterLeakTest:
649                                 if self._afterLeakTest[k]-self._beforeLeakTest[k]:
650                                         print k, self._afterLeakTest[k], self._beforeLeakTest[k], self._afterLeakTest[k] - self._beforeLeakTest[k]
651
652         def ShaderUpdate(self, v, f):
653                 s = opengl.GLShader(v, f)
654                 if s.isValid():
655                         self._objectLoadShader.release()
656                         self._objectLoadShader = s
657                         for obj in self._scene.objects():
658                                 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
659                         self.QueueRefresh()
660
661         def OnMouseDown(self,e):
662                 self._mouseX = e.GetX()
663                 self._mouseY = e.GetY()
664                 self._mouseClick3DPos = self._mouse3Dpos
665                 self._mouseClickFocus = self._focusObj
666                 if e.ButtonDClick():
667                         self._mouseState = 'doubleClick'
668                 else:
669                         self._mouseState = 'dragOrClick'
670                 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
671                 p0 -= self.getObjectCenterPos() - self._viewTarget
672                 p1 -= self.getObjectCenterPos() - self._viewTarget
673                 if self.tool.OnDragStart(p0, p1):
674                         self._mouseState = 'tool'
675                 if self._mouseState == 'dragOrClick':
676                         if e.GetButton() == 1:
677                                 if self._focusObj is not None:
678                                         self._selectObject(self._focusObj, False)
679                                         self.QueueRefresh()
680
681         def OnMouseUp(self, e):
682                 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
683                         return
684                 if self._mouseState == 'dragOrClick':
685                         if e.GetButton() == 1:
686                                 self._selectObject(self._focusObj)
687                         if e.GetButton() == 3:
688                                         menu = wx.Menu()
689                                         if self._focusObj is not None:
690                                                 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, _("Delete object")))
691                                                 self.Bind(wx.EVT_MENU, self.OnCenter, menu.Append(-1, _("Center on platform")))
692                                                 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, _("Multiply object")))
693                                                 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, _("Split object into parts")))
694                                         if ((self._selectedObj != self._focusObj and self._focusObj is not None and self._selectedObj is not None) or len(self._scene.objects()) == 2) and int(profile.getMachineSetting('extruder_amount')) > 1:
695                                                 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, _("Dual extrusion merge")))
696                                         if len(self._scene.objects()) > 0:
697                                                 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, _("Delete all objects")))
698                                         if menu.MenuItemCount > 0:
699                                                 self.PopupMenu(menu)
700                                         menu.Destroy()
701                 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
702                         self._scene.pushFree()
703                         self.sceneUpdated()
704                 elif self._mouseState == 'tool':
705                         if self.tempMatrix is not None and self._selectedObj is not None:
706                                 self._selectedObj.applyMatrix(self.tempMatrix)
707                                 self._scene.pushFree()
708                                 self._selectObject(self._selectedObj)
709                         self.tempMatrix = None
710                         self.tool.OnDragEnd()
711                         self.sceneUpdated()
712                 self._mouseState = None
713
714         def OnMouseMotion(self,e):
715                 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
716                 p0 -= self.getObjectCenterPos() - self._viewTarget
717                 p1 -= self.getObjectCenterPos() - self._viewTarget
718
719                 if e.Dragging() and self._mouseState is not None:
720                         if self._mouseState == 'tool':
721                                 self.tool.OnDrag(p0, p1)
722                         elif not e.LeftIsDown() and e.RightIsDown():
723                                 self._mouseState = 'drag'
724                                 if wx.GetKeyState(wx.WXK_SHIFT):
725                                         a = math.cos(math.radians(self._yaw)) / 3.0
726                                         b = math.sin(math.radians(self._yaw)) / 3.0
727                                         self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
728                                         self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
729                                         self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
730                                         self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
731                                 else:
732                                         self._yaw += e.GetX() - self._mouseX
733                                         self._pitch -= e.GetY() - self._mouseY
734                                 if self._pitch > 170:
735                                         self._pitch = 170
736                                 if self._pitch < 10:
737                                         self._pitch = 10
738                         elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
739                                 self._mouseState = 'drag'
740                                 self._zoom += e.GetY() - self._mouseY
741                                 if self._zoom < 1:
742                                         self._zoom = 1
743                                 if self._zoom > numpy.max(self._machineSize) * 3:
744                                         self._zoom = numpy.max(self._machineSize) * 3
745                         elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus:
746                                 self._mouseState = 'dragObject'
747                                 z = max(0, self._mouseClick3DPos[2])
748                                 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
749                                 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
750                                 p0[2] -= z
751                                 p1[2] -= z
752                                 p2[2] -= z
753                                 p3[2] -= z
754                                 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
755                                 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
756                                 diff = cursorZ1 - cursorZ0
757                                 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
758                 if not e.Dragging() or self._mouseState != 'tool':
759                         self.tool.OnMouseMove(p0, p1)
760
761                 self._mouseX = e.GetX()
762                 self._mouseY = e.GetY()
763
764         def OnMouseWheel(self, e):
765                 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
766                 delta = max(min(delta,4),-4)
767                 self._zoom *= 1.0 - delta / 10.0
768                 if self._zoom < 1.0:
769                         self._zoom = 1.0
770                 if self._zoom > numpy.max(self._machineSize) * 3:
771                         self._zoom = numpy.max(self._machineSize) * 3
772                 self.Refresh()
773
774         def OnMouseLeave(self, e):
775                 #self._mouseX = -1
776                 pass
777
778         def getMouseRay(self, x, y):
779                 if self._viewport is None:
780                         return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
781                 p0 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
782                 p1 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
783                 p0 -= self._viewTarget
784                 p1 -= self._viewTarget
785                 return p0, p1
786
787         def _init3DView(self):
788                 # set viewing projection
789                 size = self.GetSize()
790                 glViewport(0, 0, size.GetWidth(), size.GetHeight())
791                 glLoadIdentity()
792
793                 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
794
795                 glDisable(GL_RESCALE_NORMAL)
796                 glDisable(GL_LIGHTING)
797                 glDisable(GL_LIGHT0)
798                 glEnable(GL_DEPTH_TEST)
799                 glDisable(GL_CULL_FACE)
800                 glDisable(GL_BLEND)
801                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
802
803                 glClearColor(0.8, 0.8, 0.8, 1.0)
804                 glClearStencil(0)
805                 glClearDepth(1.0)
806
807                 glMatrixMode(GL_PROJECTION)
808                 glLoadIdentity()
809                 aspect = float(size.GetWidth()) / float(size.GetHeight())
810                 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
811
812                 glMatrixMode(GL_MODELVIEW)
813                 glLoadIdentity()
814                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
815
816         def OnPaint(self,e):
817                 if machineCom.machineIsConnected():
818                         self.printButton._imageID = 6
819                         self.printButton._tooltip = _("Print")
820                 elif len(removableStorage.getPossibleSDcardDrives()) > 0:
821                         self.printButton._imageID = 2
822                         self.printButton._tooltip = _("Toolpath to SD")
823                 else:
824                         self.printButton._imageID = 3
825                         self.printButton._tooltip = _("Save toolpath")
826
827                 if self._animView is not None:
828                         self._viewTarget = self._animView.getPosition()
829                         if self._animView.isDone():
830                                 self._animView = None
831                 if self._animZoom is not None:
832                         self._zoom = self._animZoom.getPosition()
833                         if self._animZoom.isDone():
834                                 self._animZoom = None
835                 if self.viewMode == 'gcode' and self._gcode is not None:
836                         try:
837                                 self._viewTarget[2] = self._gcode.layerList[self.layerSelect.getValue()][-1]['points'][0][2]
838                         except:
839                                 pass
840                 if self._objectShader is None:
841                         if opengl.hasShaderSupport():
842                                 self._objectShader = opengl.GLShader("""
843 varying float light_amount;
844
845 void main(void)
846 {
847     gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
848     gl_FrontColor = gl_Color;
849
850         light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
851         light_amount += 0.2;
852 }
853                                 ""","""
854 varying float light_amount;
855
856 void main(void)
857 {
858         gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
859 }
860                                 """)
861                                 self._objectOverhangShader = opengl.GLShader("""
862 uniform float cosAngle;
863 uniform mat3 rotMatrix;
864 varying float light_amount;
865
866 void main(void)
867 {
868     gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
869     gl_FrontColor = gl_Color;
870
871         light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
872         light_amount += 0.2;
873         if (normalize(rotMatrix * gl_Normal).z < -cosAngle)
874         {
875                 light_amount = -10.0;
876         }
877 }
878                                 ""","""
879 varying float light_amount;
880
881 void main(void)
882 {
883         if (light_amount == -10.0)
884         {
885                 gl_FragColor = vec4(1.0, 0.0, 0.0, gl_Color[3]);
886         }else{
887                 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
888         }
889 }
890                                 """)
891                                 self._objectLoadShader = opengl.GLShader("""
892 uniform float intensity;
893 uniform float scale;
894 varying float light_amount;
895
896 void main(void)
897 {
898         vec4 tmp = gl_Vertex;
899     tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
900     tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
901     gl_Position = gl_ModelViewProjectionMatrix * tmp;
902     gl_FrontColor = gl_Color;
903
904         light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
905         light_amount += 0.2;
906 }
907                         ""","""
908 uniform float intensity;
909 varying float light_amount;
910
911 void main(void)
912 {
913         gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
914 }
915                                 """)
916                         if self._objectShader is None or not self._objectShader.isValid():
917                                 self._objectShader = opengl.GLFakeShader()
918                                 self._objectOverhangShader = opengl.GLFakeShader()
919                                 self._objectLoadShader = None
920                 self._init3DView()
921                 glTranslate(0,0,-self._zoom)
922                 glRotate(-self._pitch, 1,0,0)
923                 glRotate(self._yaw, 0,0,1)
924                 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
925
926                 self._viewport = glGetIntegerv(GL_VIEWPORT)
927                 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
928                 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
929
930                 glClearColor(1,1,1,1)
931                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
932
933                 if self.viewMode != 'gcode':
934                         for n in xrange(0, len(self._scene.objects())):
935                                 obj = self._scene.objects()[n]
936                                 glColor4ub((n >> 16) & 0xFF, (n >> 8) & 0xFF, (n >> 0) & 0xFF, 0xFF)
937                                 self._renderObject(obj)
938
939                 if self._mouseX > -1:
940                         glFlush()
941                         n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0] >> 8
942                         if n < len(self._scene.objects()):
943                                 self._focusObj = self._scene.objects()[n]
944                         else:
945                                 self._focusObj = None
946                         f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
947                         #self.GetTopLevelParent().SetTitle(hex(n) + " " + str(f))
948                         self._mouse3Dpos = opengl.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
949                         self._mouse3Dpos -= self._viewTarget
950
951                 self._init3DView()
952                 glTranslate(0,0,-self._zoom)
953                 glRotate(-self._pitch, 1,0,0)
954                 glRotate(self._yaw, 0,0,1)
955                 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
956
957                 if self.viewMode == 'gcode':
958                         if self._gcode is not None and self._gcode.layerList is None:
959                                 self._gcodeLoadThread = threading.Thread(target=self._loadGCode)
960                                 self._gcodeLoadThread.daemon = True
961                                 self._gcodeLoadThread.start()
962                         if self._gcode is not None and self._gcode.layerList is not None:
963                                 glPushMatrix()
964                                 if profile.getMachineSetting('machine_center_is_zero') != 'True':
965                                         glTranslate(-self._machineSize[0] / 2, -self._machineSize[1] / 2, 0)
966                                 t = time.time()
967                                 drawUpTill = min(len(self._gcode.layerList), self.layerSelect.getValue() + 1)
968                                 for n in xrange(0, drawUpTill):
969                                         c = 1.0 - float(drawUpTill - n) / 15
970                                         c = max(0.3, c)
971                                         if len(self._gcodeVBOs) < n + 1:
972                                                 self._gcodeVBOs.append(self._generateGCodeVBOs(self._gcode.layerList[n]))
973                                                 if time.time() - t > 0.5:
974                                                         self.QueueRefresh()
975                                                         break
976                                         #['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']
977                                         if n == drawUpTill - 1:
978                                                 if len(self._gcodeVBOs[n]) < 9:
979                                                         self._gcodeVBOs[n] += self._generateGCodeVBOs2(self._gcode.layerList[n])
980                                                 glColor3f(c, 0, 0)
981                                                 self._gcodeVBOs[n][8].render(GL_QUADS)
982                                                 glColor3f(c/2, 0, c)
983                                                 self._gcodeVBOs[n][9].render(GL_QUADS)
984                                                 glColor3f(0, c, c/2)
985                                                 self._gcodeVBOs[n][10].render(GL_QUADS)
986                                                 glColor3f(c, 0, 0)
987                                                 self._gcodeVBOs[n][11].render(GL_QUADS)
988
989                                                 glColor3f(0, c, 0)
990                                                 self._gcodeVBOs[n][12].render(GL_QUADS)
991                                                 glColor3f(c/2, c/2, 0.0)
992                                                 self._gcodeVBOs[n][13].render(GL_QUADS)
993                                                 glColor3f(0, c, c)
994                                                 self._gcodeVBOs[n][14].render(GL_QUADS)
995                                                 self._gcodeVBOs[n][15].render(GL_QUADS)
996                                                 glColor3f(0, 0, c)
997                                                 self._gcodeVBOs[n][16].render(GL_LINES)
998                                         else:
999                                                 glColor3f(c, 0, 0)
1000                                                 self._gcodeVBOs[n][0].render(GL_LINES)
1001                                                 glColor3f(c/2, 0, c)
1002                                                 self._gcodeVBOs[n][1].render(GL_LINES)
1003                                                 glColor3f(0, c, c/2)
1004                                                 self._gcodeVBOs[n][2].render(GL_LINES)
1005                                                 glColor3f(c, 0, 0)
1006                                                 self._gcodeVBOs[n][3].render(GL_LINES)
1007
1008                                                 glColor3f(0, c, 0)
1009                                                 self._gcodeVBOs[n][4].render(GL_LINES)
1010                                                 glColor3f(c/2, c/2, 0.0)
1011                                                 self._gcodeVBOs[n][5].render(GL_LINES)
1012                                                 glColor3f(0, c, c)
1013                                                 self._gcodeVBOs[n][6].render(GL_LINES)
1014                                                 self._gcodeVBOs[n][7].render(GL_LINES)
1015                                 glPopMatrix()
1016                 else:
1017                         glStencilFunc(GL_ALWAYS, 1, 1)
1018                         glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1019
1020                         if self.viewMode == 'overhang':
1021                                 self._objectOverhangShader.bind()
1022                                 self._objectOverhangShader.setUniform('cosAngle', math.cos(math.radians(90 - 60)))
1023                         else:
1024                                 self._objectShader.bind()
1025                         for obj in self._scene.objects():
1026                                 if obj._loadAnim is not None:
1027                                         if obj._loadAnim.isDone():
1028                                                 obj._loadAnim = None
1029                                         else:
1030                                                 continue
1031                                 brightness = 1.0
1032                                 if self._focusObj == obj:
1033                                         brightness = 1.2
1034                                 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
1035                                         brightness = 0.8
1036
1037                                 if self._selectedObj == obj or self._selectedObj is None:
1038                                         #If we want transparent, then first render a solid black model to remove the printer size lines.
1039                                         if self.viewMode == 'transparent':
1040                                                 glColor4f(0, 0, 0, 0)
1041                                                 self._renderObject(obj)
1042                                                 glEnable(GL_BLEND)
1043                                                 glBlendFunc(GL_ONE, GL_ONE)
1044                                                 glDisable(GL_DEPTH_TEST)
1045                                                 brightness *= 0.5
1046                                         if self.viewMode == 'xray':
1047                                                 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
1048                                         glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1049                                         glEnable(GL_STENCIL_TEST)
1050
1051                                 if self.viewMode == 'overhang':
1052                                         if self._selectedObj == obj and self.tempMatrix is not None:
1053                                                 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix() * self.tempMatrix)
1054                                         else:
1055                                                 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix())
1056
1057                                 if not self._scene.checkPlatform(obj):
1058                                         glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
1059                                         self._renderObject(obj)
1060                                 else:
1061                                         self._renderObject(obj, brightness)
1062                                 glDisable(GL_STENCIL_TEST)
1063                                 glDisable(GL_BLEND)
1064                                 glEnable(GL_DEPTH_TEST)
1065                                 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
1066
1067                         if self.viewMode == 'xray':
1068                                 glPushMatrix()
1069                                 glLoadIdentity()
1070                                 glEnable(GL_STENCIL_TEST)
1071                                 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
1072                                 glDisable(GL_DEPTH_TEST)
1073                                 for i in xrange(2, 15, 2):
1074                                         glStencilFunc(GL_EQUAL, i, 0xFF)
1075                                         glColor(float(i)/10, float(i)/10, float(i)/5)
1076                                         glBegin(GL_QUADS)
1077                                         glVertex3f(-1000,-1000,-10)
1078                                         glVertex3f( 1000,-1000,-10)
1079                                         glVertex3f( 1000, 1000,-10)
1080                                         glVertex3f(-1000, 1000,-10)
1081                                         glEnd()
1082                                 for i in xrange(1, 15, 2):
1083                                         glStencilFunc(GL_EQUAL, i, 0xFF)
1084                                         glColor(float(i)/10, 0, 0)
1085                                         glBegin(GL_QUADS)
1086                                         glVertex3f(-1000,-1000,-10)
1087                                         glVertex3f( 1000,-1000,-10)
1088                                         glVertex3f( 1000, 1000,-10)
1089                                         glVertex3f(-1000, 1000,-10)
1090                                         glEnd()
1091                                 glPopMatrix()
1092                                 glDisable(GL_STENCIL_TEST)
1093                                 glEnable(GL_DEPTH_TEST)
1094
1095                         self._objectShader.unbind()
1096
1097                         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1098                         glEnable(GL_BLEND)
1099                         if self._objectLoadShader is not None:
1100                                 self._objectLoadShader.bind()
1101                                 glColor4f(0.2, 0.6, 1.0, 1.0)
1102                                 for obj in self._scene.objects():
1103                                         if obj._loadAnim is None:
1104                                                 continue
1105                                         self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
1106                                         self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
1107                                         self._renderObject(obj)
1108                                 self._objectLoadShader.unbind()
1109                                 glDisable(GL_BLEND)
1110
1111                 self._drawMachine()
1112
1113                 if self._usbPrintMonitor.getState() == 'PRINTING' and self._usbPrintMonitor.getID() == self._slicer.getID():
1114                         glEnable(GL_BLEND)
1115                         z = self._usbPrintMonitor.getZ()
1116                         size = self._machineSize
1117                         glColor4ub(255,255,0,128)
1118                         glBegin(GL_QUADS)
1119                         glVertex3f(-size[0]/2,-size[1]/2, z)
1120                         glVertex3f( size[0]/2,-size[1]/2, z)
1121                         glVertex3f( size[0]/2, size[1]/2, z)
1122                         glVertex3f(-size[0]/2, size[1]/2, z)
1123                         glEnd()
1124
1125                 if self.viewMode == 'gcode':
1126                         if self._gcodeLoadThread is not None and self._gcodeLoadThread.isAlive():
1127                                 glDisable(GL_DEPTH_TEST)
1128                                 glPushMatrix()
1129                                 glLoadIdentity()
1130                                 glTranslate(0,-4,-10)
1131                                 glColor4ub(60,60,60,255)
1132                                 opengl.glDrawStringCenter(_("Loading toolpath for visualization..."))
1133                                 glPopMatrix()
1134                 else:
1135                         #Draw the object box-shadow, so you can see where it will collide with other objects.
1136                         if self._selectedObj is not None and len(self._scene.objects()) > 1:
1137                                 size = self._selectedObj.getSize()[0:2] / 2 + self._scene.getObjectExtend()
1138                                 glPushMatrix()
1139                                 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0)
1140                                 glEnable(GL_BLEND)
1141                                 glEnable(GL_CULL_FACE)
1142                                 glColor4f(0,0,0,0.12)
1143                                 glBegin(GL_QUADS)
1144                                 glVertex3f(-size[0],  size[1], 0.1)
1145                                 glVertex3f(-size[0], -size[1], 0.1)
1146                                 glVertex3f( size[0], -size[1], 0.1)
1147                                 glVertex3f( size[0],  size[1], 0.1)
1148                                 glEnd()
1149                                 glDisable(GL_CULL_FACE)
1150                                 glPopMatrix()
1151
1152                         #Draw the outline of the selected object, on top of everything else except the GUI.
1153                         if self._selectedObj is not None and self._selectedObj._loadAnim is None:
1154                                 glDisable(GL_DEPTH_TEST)
1155                                 glEnable(GL_CULL_FACE)
1156                                 glEnable(GL_STENCIL_TEST)
1157                                 glDisable(GL_BLEND)
1158                                 glStencilFunc(GL_EQUAL, 0, 255)
1159
1160                                 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
1161                                 glLineWidth(2)
1162                                 glColor4f(1,1,1,0.5)
1163                                 self._renderObject(self._selectedObj)
1164                                 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
1165
1166                                 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
1167                                 glDisable(GL_STENCIL_TEST)
1168                                 glDisable(GL_CULL_FACE)
1169                                 glEnable(GL_DEPTH_TEST)
1170
1171                         if self._selectedObj is not None:
1172                                 glPushMatrix()
1173                                 pos = self.getObjectCenterPos()
1174                                 glTranslate(pos[0], pos[1], pos[2])
1175                                 self.tool.OnDraw()
1176                                 glPopMatrix()
1177                 if self.viewMode == 'overhang' and not opengl.hasShaderSupport():
1178                         glDisable(GL_DEPTH_TEST)
1179                         glPushMatrix()
1180                         glLoadIdentity()
1181                         glTranslate(0,-4,-10)
1182                         glColor4ub(60,60,60,255)
1183                         opengl.glDrawStringCenter(_("Overhang view not working due to lack of OpenGL shaders support."))
1184                         glPopMatrix()
1185
1186         def _renderObject(self, obj, brightness = False, addSink = True):
1187                 glPushMatrix()
1188                 if addSink:
1189                         glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2 - profile.getProfileSettingFloat('object_sink'))
1190                 else:
1191                         glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
1192
1193                 if self.tempMatrix is not None and obj == self._selectedObj:
1194                         tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
1195                         glMultMatrixf(tempMatrix)
1196
1197                 offset = obj.getDrawOffset()
1198                 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
1199
1200                 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
1201                 glMultMatrixf(tempMatrix)
1202
1203                 n = 0
1204                 for m in obj._meshList:
1205                         if m.vbo is None:
1206                                 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
1207                         if brightness:
1208                                 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
1209                                 n += 1
1210                         m.vbo.render()
1211                 glPopMatrix()
1212
1213         def _drawMachine(self):
1214                 glEnable(GL_CULL_FACE)
1215                 glEnable(GL_BLEND)
1216
1217                 size = [profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')]
1218
1219                 machine = profile.getMachineSetting('machine_type')
1220                 if profile.getMachineSetting('machine_type').startswith('ultimaker'):
1221                         if machine not in self._platformMesh:
1222                                 meshes = meshLoader.loadMeshes(resources.getPathForMesh(machine + '_platform.stl'))
1223                                 if len(meshes) > 0:
1224                                         self._platformMesh[machine] = meshes[0]
1225                                 else:
1226                                         self._platformMesh[machine] = None
1227                                 if machine == 'ultimaker2':
1228                                         self._platformMesh[machine]._drawOffset = numpy.array([0,-37,145], numpy.float32)
1229                                 else:
1230                                         self._platformMesh[machine]._drawOffset = numpy.array([0,0,2.5], numpy.float32)
1231                         glColor4f(1,1,1,0.5)
1232                         self._objectShader.bind()
1233                         self._renderObject(self._platformMesh[machine], False, False)
1234                         self._objectShader.unbind()
1235
1236                         #For the Ultimaker 2 render the texture on the back plate to show the Ultimaker2 text.
1237                         if machine == 'ultimaker2':
1238                                 if not hasattr(self._platformMesh[machine], 'texture'):
1239                                         self._platformMesh[machine].texture = opengl.loadGLTexture('Ultimaker2backplate.png')
1240                                 glBindTexture(GL_TEXTURE_2D, self._platformMesh[machine].texture)
1241                                 glEnable(GL_TEXTURE_2D)
1242                                 glPushMatrix()
1243                                 glColor4f(1,1,1,1)
1244
1245                                 glTranslate(0,150,-5)
1246                                 h = 50
1247                                 d = 8
1248                                 w = 100
1249                                 glEnable(GL_BLEND)
1250                                 glBlendFunc(GL_DST_COLOR, GL_ZERO)
1251                                 glBegin(GL_QUADS)
1252                                 glTexCoord2f(1, 0)
1253                                 glVertex3f( w, 0, h)
1254                                 glTexCoord2f(0, 0)
1255                                 glVertex3f(-w, 0, h)
1256                                 glTexCoord2f(0, 1)
1257                                 glVertex3f(-w, 0, 0)
1258                                 glTexCoord2f(1, 1)
1259                                 glVertex3f( w, 0, 0)
1260
1261                                 glTexCoord2f(1, 0)
1262                                 glVertex3f(-w, d, h)
1263                                 glTexCoord2f(0, 0)
1264                                 glVertex3f( w, d, h)
1265                                 glTexCoord2f(0, 1)
1266                                 glVertex3f( w, d, 0)
1267                                 glTexCoord2f(1, 1)
1268                                 glVertex3f(-w, d, 0)
1269                                 glEnd()
1270                                 glDisable(GL_TEXTURE_2D)
1271                                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1272                                 glPopMatrix()
1273                 else:
1274                         glColor4f(0,0,0,1)
1275                         glLineWidth(3)
1276                         glBegin(GL_LINES)
1277                         glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1278                         glVertex3f(-size[0] / 2, -size[1] / 2, 10)
1279                         glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1280                         glVertex3f(-size[0] / 2+10, -size[1] / 2, 0)
1281                         glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1282                         glVertex3f(-size[0] / 2, -size[1] / 2+10, 0)
1283                         glEnd()
1284
1285                 #Cornerpoints for big blue square
1286                 v0 = [ size[0] / 2, size[1] / 2, size[2]]
1287                 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
1288                 v2 = [-size[0] / 2, size[1] / 2, size[2]]
1289                 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
1290                 v4 = [ size[0] / 2, size[1] / 2, 0]
1291                 v5 = [ size[0] / 2,-size[1] / 2, 0]
1292                 v6 = [-size[0] / 2, size[1] / 2, 0]
1293                 v7 = [-size[0] / 2,-size[1] / 2, 0]
1294
1295                 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
1296                 glEnableClientState(GL_VERTEX_ARRAY)
1297                 glVertexPointer(3, GL_FLOAT, 3*4, vList)
1298
1299                 glColor4ub(5, 171, 231, 64)
1300                 glDrawArrays(GL_QUADS, 0, 4)
1301                 glColor4ub(5, 171, 231, 96)
1302                 glDrawArrays(GL_QUADS, 4, 8)
1303                 glColor4ub(5, 171, 231, 128)
1304                 glDrawArrays(GL_QUADS, 12, 8)
1305                 glDisableClientState(GL_VERTEX_ARRAY)
1306
1307                 #Draw checkerboard
1308                 sx = self._machineSize[0]
1309                 sy = self._machineSize[1]
1310                 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
1311                         for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
1312                                 x1 = x * 10
1313                                 x2 = x1 + 10
1314                                 y1 = y * 10
1315                                 y2 = y1 + 10
1316                                 x1 = max(min(x1, sx/2), -sx/2)
1317                                 y1 = max(min(y1, sy/2), -sy/2)
1318                                 x2 = max(min(x2, sx/2), -sx/2)
1319                                 y2 = max(min(y2, sy/2), -sy/2)
1320                                 #Black or "white"  checker
1321                                 if (x & 1) == (y & 1):
1322                                         glColor4ub(5, 171, 231, 127)
1323                                 else:
1324                                         glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
1325                                 glBegin(GL_QUADS)
1326                                 glVertex3f(x1, y1, -0.02)
1327                                 glVertex3f(x2, y1, -0.02)
1328                                 glVertex3f(x2, y2, -0.02)
1329                                 glVertex3f(x1, y2, -0.02)
1330                                 glEnd()
1331                 #if UM2, draw bat-area zone for head. THe head can't stop there, because its bat-area.
1332                 glDisable(GL_BLEND)
1333                 glDisable(GL_CULL_FACE)
1334
1335         def _generateGCodeVBOs(self, layer):
1336                 ret = []
1337                 for extrudeType in ['WALL-OUTER:0', 'WALL-OUTER:1', 'WALL-OUTER:2', 'WALL-OUTER:3', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1338                         if ':' in extrudeType:
1339                                 extruder = int(extrudeType[extrudeType.find(':')+1:])
1340                                 extrudeType = extrudeType[0:extrudeType.find(':')]
1341                         else:
1342                                 extruder = None
1343                         pointList = numpy.zeros((0,3), numpy.float32)
1344                         for path in layer:
1345                                 if path['type'] == 'extrude' and path['pathType'] == extrudeType and (extruder is None or path['extruder'] == extruder):
1346                                         a = path['points']
1347                                         a = numpy.concatenate((a[:-1], a[1:]), 1)
1348                                         a = a.reshape((len(a) * 2, 3))
1349                                         pointList = numpy.concatenate((pointList, a))
1350                         ret.append(opengl.GLVBO(pointList))
1351                 return ret
1352
1353         def _generateGCodeVBOs2(self, layer):
1354                 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
1355                 filamentArea = math.pi * filamentRadius * filamentRadius
1356                 useFilamentArea = profile.getMachineSetting('gcode_flavor') == 'UltiGCode'
1357
1358                 ret = []
1359                 for extrudeType in ['WALL-OUTER:0', 'WALL-OUTER:1', 'WALL-OUTER:2', 'WALL-OUTER:3', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1360                         if ':' in extrudeType:
1361                                 extruder = int(extrudeType[extrudeType.find(':')+1:])
1362                                 extrudeType = extrudeType[0:extrudeType.find(':')]
1363                         else:
1364                                 extruder = None
1365                         pointList = numpy.zeros((0,3), numpy.float32)
1366                         for path in layer:
1367                                 if path['type'] == 'extrude' and path['pathType'] == extrudeType and (extruder is None or path['extruder'] == extruder):
1368                                         a = path['points']
1369                                         if extrudeType == 'FILL':
1370                                                 a[:,2] += 0.01
1371
1372                                         normal = a[1:] - a[:-1]
1373                                         lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
1374                                         normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
1375                                         normal[:,2] /= lens
1376
1377                                         ePerDist = path['extrusion'][1:] / lens
1378                                         if useFilamentArea:
1379                                                 lineWidth = ePerDist / path['layerThickness'] / 2.0
1380                                         else:
1381                                                 lineWidth = ePerDist * (filamentArea / path['layerThickness'] / 2)
1382
1383                                         normal[:,0] *= lineWidth
1384                                         normal[:,1] *= lineWidth
1385
1386                                         b = numpy.zeros((len(a)-1, 0), numpy.float32)
1387                                         b = numpy.concatenate((b, a[1:] + normal), 1)
1388                                         b = numpy.concatenate((b, a[1:] - normal), 1)
1389                                         b = numpy.concatenate((b, a[:-1] - normal), 1)
1390                                         b = numpy.concatenate((b, a[:-1] + normal), 1)
1391                                         b = b.reshape((len(b) * 4, 3))
1392
1393                                         if len(a) > 2:
1394                                                 normal2 = normal[:-1] + normal[1:]
1395                                                 lens2 = numpy.sqrt(normal2[:,0]**2 + normal2[:,1]**2)
1396                                                 normal2[:,0] /= lens2
1397                                                 normal2[:,1] /= lens2
1398                                                 normal2[:,0] *= lineWidth[:-1]
1399                                                 normal2[:,1] *= lineWidth[:-1]
1400
1401                                                 c = numpy.zeros((len(a)-2, 0), numpy.float32)
1402                                                 c = numpy.concatenate((c, a[1:-1]), 1)
1403                                                 c = numpy.concatenate((c, a[1:-1]+normal[1:]), 1)
1404                                                 c = numpy.concatenate((c, a[1:-1]+normal2), 1)
1405                                                 c = numpy.concatenate((c, a[1:-1]+normal[:-1]), 1)
1406
1407                                                 c = numpy.concatenate((c, a[1:-1]), 1)
1408                                                 c = numpy.concatenate((c, a[1:-1]-normal[1:]), 1)
1409                                                 c = numpy.concatenate((c, a[1:-1]-normal2), 1)
1410                                                 c = numpy.concatenate((c, a[1:-1]-normal[:-1]), 1)
1411
1412                                                 c = c.reshape((len(c) * 8, 3))
1413
1414                                                 pointList = numpy.concatenate((pointList, b, c))
1415                                         else:
1416                                                 pointList = numpy.concatenate((pointList, b))
1417                         ret.append(opengl.GLVBO(pointList))
1418
1419                 pointList = numpy.zeros((0,3), numpy.float32)
1420                 for path in layer:
1421                         if path['type'] == 'move':
1422                                 a = path['points'] + numpy.array([0,0,0.01], numpy.float32)
1423                                 a = numpy.concatenate((a[:-1], a[1:]), 1)
1424                                 a = a.reshape((len(a) * 2, 3))
1425                                 pointList = numpy.concatenate((pointList, a))
1426                         if path['type'] == 'retract':
1427                                 a = path['points'] + numpy.array([0,0,0.01], numpy.float32)
1428                                 a = numpy.concatenate((a[:-1], a[1:] + numpy.array([0,0,1], numpy.float32)), 1)
1429                                 a = a.reshape((len(a) * 2, 3))
1430                                 pointList = numpy.concatenate((pointList, a))
1431                 ret.append(opengl.GLVBO(pointList))
1432
1433                 return ret
1434
1435         def getObjectCenterPos(self):
1436                 if self._selectedObj is None:
1437                         return [0.0, 0.0, 0.0]
1438                 pos = self._selectedObj.getPosition()
1439                 size = self._selectedObj.getSize()
1440                 return [pos[0], pos[1], size[2]/2 - profile.getProfileSettingFloat('object_sink')]
1441
1442         def getObjectBoundaryCircle(self):
1443                 if self._selectedObj is None:
1444                         return 0.0
1445                 return self._selectedObj.getBoundaryCircle()
1446
1447         def getObjectSize(self):
1448                 if self._selectedObj is None:
1449                         return [0.0, 0.0, 0.0]
1450                 return self._selectedObj.getSize()
1451
1452         def getObjectMatrix(self):
1453                 if self._selectedObj is None:
1454                         return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1455                 return self._selectedObj.getMatrix()
1456
1457 class shaderEditor(wx.Dialog):
1458         def __init__(self, parent, callback, v, f):
1459                 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1460                 self._callback = callback
1461                 s = wx.BoxSizer(wx.VERTICAL)
1462                 self.SetSizer(s)
1463                 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1464                 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1465                 s.Add(self._vertex, 1, flag=wx.EXPAND)
1466                 s.Add(self._fragment, 1, flag=wx.EXPAND)
1467
1468                 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1469                 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1470
1471                 self.SetPosition(self.GetParent().GetPosition())
1472                 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1473                 self.Show()
1474
1475         def OnText(self, e):
1476                 self._callback(self._vertex.GetValue(), self._fragment.GetValue())