chiark / gitweb /
Add "Reload platform" to refresh all the objects on the platform from their files
[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 reloadScene(self, e):
198                 # Copy the list before DeleteAll clears it
199                 fileList = []
200                 for obj in self._scene.objects():
201                         fileList.append(obj.getOriginFilename())
202                 self.OnDeleteAll(None)
203                 self.loadScene(fileList)
204
205         def showLoadModel(self, button = 1):
206                 if button == 1:
207                         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)
208                         dlg.SetWildcard(meshLoader.loadWildcardFilter() + imageToMesh.wildcardList() + "|GCode file (*.gcode)|*.g;*.gcode;*.G;*.GCODE")
209                         if dlg.ShowModal() != wx.ID_OK:
210                                 dlg.Destroy()
211                                 return
212                         filenames = dlg.GetPaths()
213                         dlg.Destroy()
214                         if len(filenames) < 1:
215                                 return False
216                         profile.putPreference('lastFile', filenames[0])
217                         self.loadFiles(filenames)
218
219         def showSaveModel(self):
220                 if len(self._scene.objects()) < 1:
221                         return
222                 dlg=wx.FileDialog(self, _("Save 3D model"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
223                 dlg.SetWildcard(meshLoader.saveWildcardFilter())
224                 if dlg.ShowModal() != wx.ID_OK:
225                         dlg.Destroy()
226                         return
227                 filename = dlg.GetPath()
228                 dlg.Destroy()
229                 meshLoader.saveMeshes(filename, self._scene.objects())
230
231         def OnPrintButton(self, button):
232                 if button == 1:
233                         if machineCom.machineIsConnected():
234                                 self.showPrintWindow()
235                         elif len(removableStorage.getPossibleSDcardDrives()) > 0:
236                                 drives = removableStorage.getPossibleSDcardDrives()
237                                 if len(drives) > 1:
238                                         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))
239                                         if dlg.ShowModal() != wx.ID_OK:
240                                                 dlg.Destroy()
241                                                 return
242                                         drive = drives[dlg.GetSelection()]
243                                         dlg.Destroy()
244                                 else:
245                                         drive = drives[0]
246                                 filename = self._scene._objectList[0].getName() + '.gcode'
247                                 threading.Thread(target=self._copyFile,args=(self._gcodeFilename, drive[1] + filename, drive[1])).start()
248                         else:
249                                 self.showSaveGCode()
250                 if button == 3:
251                         menu = wx.Menu()
252                         self.Bind(wx.EVT_MENU, lambda e: self.showPrintWindow(), menu.Append(-1, _("Print with USB")))
253                         self.Bind(wx.EVT_MENU, lambda e: self.showSaveGCode(), menu.Append(-1, _("Save GCode...")))
254                         self.Bind(wx.EVT_MENU, lambda e: self._showSliceLog(), menu.Append(-1, _("Slice engine log...")))
255                         self.PopupMenu(menu)
256                         menu.Destroy()
257
258         def showPrintWindow(self):
259                 if self._gcodeFilename is None:
260                         return
261                 self._usbPrintMonitor.loadFile(self._gcodeFilename, self._slicer.getID())
262                 if self._gcodeFilename == self._slicer.getGCodeFilename():
263                         self._slicer.submitSliceInfoOnline()
264
265         def showSaveGCode(self):
266                 if len(self._scene._objectList) < 1:
267                         return
268                 dlg=wx.FileDialog(self, _("Save toolpath"), os.path.dirname(profile.getPreference('lastFile')), style=wx.FD_SAVE)
269                 filename = self._scene._objectList[0].getName() + '.gcode'
270                 dlg.SetFilename(filename)
271                 dlg.SetWildcard('Toolpath (*.gcode)|*.gcode;*.g')
272                 if dlg.ShowModal() != wx.ID_OK:
273                         dlg.Destroy()
274                         return
275                 filename = dlg.GetPath()
276                 dlg.Destroy()
277
278                 threading.Thread(target=self._copyFile,args=(self._gcodeFilename, filename)).start()
279
280         def _copyFile(self, fileA, fileB, allowEject = False):
281                 try:
282                         size = float(os.stat(fileA).st_size)
283                         with open(fileA, 'rb') as fsrc:
284                                 with open(fileB, 'wb') as fdst:
285                                         while 1:
286                                                 buf = fsrc.read(16*1024)
287                                                 if not buf:
288                                                         break
289                                                 fdst.write(buf)
290                                                 self.printButton.setProgressBar(float(fsrc.tell()) / size)
291                                                 self._queueRefresh()
292                 except:
293                         import sys
294                         print sys.exc_info()
295                         self.notification.message("Failed to save")
296                 else:
297                         if allowEject:
298                                 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...'))
299                         else:
300                                 self.notification.message("Saved as %s" % (fileB))
301                 self.printButton.setProgressBar(None)
302                 if fileA == self._slicer.getGCodeFilename():
303                         self._slicer.submitSliceInfoOnline()
304
305         def _showSliceLog(self):
306                 dlg = wx.TextEntryDialog(self, _("The slicing engine reported the following"), _("Engine log..."), '\n'.join(self._slicer.getSliceLog()), wx.TE_MULTILINE | wx.OK | wx.CENTRE)
307                 dlg.ShowModal()
308                 dlg.Destroy()
309
310         def OnToolSelect(self, button):
311                 if self.rotateToolButton.getSelected():
312                         self.tool = previewTools.toolRotate(self)
313                 elif self.scaleToolButton.getSelected():
314                         self.tool = previewTools.toolScale(self)
315                 elif self.mirrorToolButton.getSelected():
316                         self.tool = previewTools.toolNone(self)
317                 else:
318                         self.tool = previewTools.toolNone(self)
319                 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
320                 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
321                 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
322                 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
323                 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
324                 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
325                 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
326                 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
327
328         def updateToolButtons(self):
329                 if self._selectedObj is None:
330                         hidden = True
331                 else:
332                         hidden = False
333                 self.rotateToolButton.setHidden(hidden)
334                 self.scaleToolButton.setHidden(hidden)
335                 self.mirrorToolButton.setHidden(hidden)
336                 if hidden:
337                         self.rotateToolButton.setSelected(False)
338                         self.scaleToolButton.setSelected(False)
339                         self.mirrorToolButton.setSelected(False)
340                         self.OnToolSelect(0)
341
342         def OnViewChange(self):
343                 if self.viewSelection.getValue() == 4:
344                         self.viewMode = 'gcode'
345                         if self._gcode is not None and self._gcode.layerList is not None:
346                                 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
347                         self._selectObject(None)
348                 elif self.viewSelection.getValue() == 1:
349                         self.viewMode = 'overhang'
350                 elif self.viewSelection.getValue() == 2:
351                         self.viewMode = 'transparent'
352                 elif self.viewSelection.getValue() == 3:
353                         self.viewMode = 'xray'
354                 else:
355                         self.viewMode = 'normal'
356                 self.layerSelect.setHidden(self.viewMode != 'gcode')
357                 self.QueueRefresh()
358
359         def OnRotateReset(self, button):
360                 if self._selectedObj is None:
361                         return
362                 self._selectedObj.resetRotation()
363                 self._scene.pushFree()
364                 self._selectObject(self._selectedObj)
365                 self.sceneUpdated()
366
367         def OnLayFlat(self, button):
368                 if self._selectedObj is None:
369                         return
370                 self._selectedObj.layFlat()
371                 self._scene.pushFree()
372                 self._selectObject(self._selectedObj)
373                 self.sceneUpdated()
374
375         def OnScaleReset(self, button):
376                 if self._selectedObj is None:
377                         return
378                 self._selectedObj.resetScale()
379                 self._selectObject(self._selectedObj)
380                 self.updateProfileToControls()
381                 self.sceneUpdated()
382
383         def OnScaleMax(self, button):
384                 if self._selectedObj is None:
385                         return
386                 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([1,1,1], numpy.float32))
387                 self._scene.pushFree()
388                 self._selectObject(self._selectedObj)
389                 self.updateProfileToControls()
390                 self.sceneUpdated()
391
392         def OnMirror(self, axis):
393                 if self._selectedObj is None:
394                         return
395                 self._selectedObj.mirror(axis)
396                 self.sceneUpdated()
397
398         def OnScaleEntry(self, value, axis):
399                 if self._selectedObj is None:
400                         return
401                 try:
402                         value = float(value)
403                 except:
404                         return
405                 self._selectedObj.setScale(value, axis, self.scaleUniform.getValue())
406                 self.updateProfileToControls()
407                 self._scene.pushFree()
408                 self._selectObject(self._selectedObj)
409                 self.sceneUpdated()
410
411         def OnScaleEntryMM(self, value, axis):
412                 if self._selectedObj is None:
413                         return
414                 try:
415                         value = float(value)
416                 except:
417                         return
418                 self._selectedObj.setSize(value, axis, self.scaleUniform.getValue())
419                 self.updateProfileToControls()
420                 self._scene.pushFree()
421                 self._selectObject(self._selectedObj)
422                 self.sceneUpdated()
423
424         def OnDeleteAll(self, e):
425                 while len(self._scene.objects()) > 0:
426                         self._deleteObject(self._scene.objects()[0])
427                 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
428
429         def OnMultiply(self, e):
430                 if self._focusObj is None:
431                         return
432                 obj = self._focusObj
433                 dlg = wx.NumberEntryDialog(self, _("How many copies do you want?"), _("Number of copies"), _("Multiply"), 1, 1, 100)
434                 if dlg.ShowModal() != wx.ID_OK:
435                         dlg.Destroy()
436                         return
437                 cnt = dlg.GetValue()
438                 dlg.Destroy()
439                 n = 0
440                 while True:
441                         n += 1
442                         newObj = obj.copy()
443                         self._scene.add(newObj)
444                         self._scene.centerAll()
445                         if not self._scene.checkPlatform(newObj):
446                                 break
447                         if n > cnt:
448                                 break
449                 if n <= cnt:
450                         self.notification.message("Could not create more then %d items" % (n - 1))
451                 self._scene.remove(newObj)
452                 self._scene.centerAll()
453                 self.sceneUpdated()
454
455         def OnSplitObject(self, e):
456                 if self._focusObj is None:
457                         return
458                 self._scene.remove(self._focusObj)
459                 for obj in self._focusObj.split(self._splitCallback):
460                         if numpy.max(obj.getSize()) > 2.0:
461                                 self._scene.add(obj)
462                 self._scene.centerAll()
463                 self._selectObject(None)
464                 self.sceneUpdated()
465
466         def OnCenter(self, e):
467                 if self._focusObj is None:
468                         return
469                 self._focusObj.setPosition(numpy.array([0.0, 0.0]))
470                 self._scene.pushFree()
471
472         def _splitCallback(self, progress):
473                 print progress
474
475         def OnMergeObjects(self, e):
476                 if self._selectedObj is None or self._focusObj is None or self._selectedObj == self._focusObj:
477                         if len(self._scene.objects()) == 2:
478                                 self._scene.merge(self._scene.objects()[0], self._scene.objects()[1])
479                                 self.sceneUpdated()
480                         return
481                 self._scene.merge(self._selectedObj, self._focusObj)
482                 self.sceneUpdated()
483
484         def sceneUpdated(self):
485                 self._sceneUpdateTimer.Start(500, True)
486                 self._slicer.abortSlicer()
487                 self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
488                 self.QueueRefresh()
489
490         def _onRunSlicer(self, e):
491                 if self._isSimpleMode:
492                         self.GetTopLevelParent().simpleSettingsPanel.setupSlice()
493                 self._slicer.runSlicer(self._scene)
494                 if self._isSimpleMode:
495                         profile.resetTempOverride()
496
497         def _updateSliceProgress(self, progressValue, ready):
498                 if not ready:
499                         if self.printButton.getProgressBar() is not None and progressValue >= 0.0 and abs(self.printButton.getProgressBar() - progressValue) < 0.01:
500                                 return
501                 self.printButton.setDisabled(not ready)
502                 if progressValue >= 0.0:
503                         self.printButton.setProgressBar(progressValue)
504                 else:
505                         self.printButton.setProgressBar(None)
506                 if self._gcode is not None:
507                         self._gcode = None
508                         for layerVBOlist in self._gcodeVBOs:
509                                 for vbo in layerVBOlist:
510                                         self.glReleaseList.append(vbo)
511                         self._gcodeVBOs = []
512                 if ready:
513                         self.printButton.setProgressBar(None)
514                         cost = self._slicer.getFilamentCost()
515                         if cost is not None:
516                                 self.printButton.setBottomText('%s\n%s\n%s' % (self._slicer.getPrintTime(), self._slicer.getFilamentAmount(), cost))
517                         else:
518                                 self.printButton.setBottomText('%s\n%s' % (self._slicer.getPrintTime(), self._slicer.getFilamentAmount()))
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 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                                                 self.Bind(wx.EVT_MENU, self.reloadScene, menu.Append(-1, _("Reload all objects")))
699                                         if menu.MenuItemCount > 0:
700                                                 self.PopupMenu(menu)
701                                         menu.Destroy()
702                 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
703                         self._scene.pushFree()
704                         self.sceneUpdated()
705                 elif self._mouseState == 'tool':
706                         if self.tempMatrix is not None and self._selectedObj is not None:
707                                 self._selectedObj.applyMatrix(self.tempMatrix)
708                                 self._scene.pushFree()
709                                 self._selectObject(self._selectedObj)
710                         self.tempMatrix = None
711                         self.tool.OnDragEnd()
712                         self.sceneUpdated()
713                 self._mouseState = None
714
715         def OnMouseMotion(self,e):
716                 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
717                 p0 -= self.getObjectCenterPos() - self._viewTarget
718                 p1 -= self.getObjectCenterPos() - self._viewTarget
719
720                 if e.Dragging() and self._mouseState is not None:
721                         if self._mouseState == 'tool':
722                                 self.tool.OnDrag(p0, p1)
723                         elif not e.LeftIsDown() and e.RightIsDown():
724                                 self._mouseState = 'drag'
725                                 if wx.GetKeyState(wx.WXK_SHIFT):
726                                         a = math.cos(math.radians(self._yaw)) / 3.0
727                                         b = math.sin(math.radians(self._yaw)) / 3.0
728                                         self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
729                                         self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
730                                         self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
731                                         self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
732                                 else:
733                                         self._yaw += e.GetX() - self._mouseX
734                                         self._pitch -= e.GetY() - self._mouseY
735                                 if self._pitch > 170:
736                                         self._pitch = 170
737                                 if self._pitch < 10:
738                                         self._pitch = 10
739                         elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
740                                 self._mouseState = 'drag'
741                                 self._zoom += e.GetY() - self._mouseY
742                                 if self._zoom < 1:
743                                         self._zoom = 1
744                                 if self._zoom > numpy.max(self._machineSize) * 3:
745                                         self._zoom = numpy.max(self._machineSize) * 3
746                         elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus:
747                                 self._mouseState = 'dragObject'
748                                 z = max(0, self._mouseClick3DPos[2])
749                                 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
750                                 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
751                                 p0[2] -= z
752                                 p1[2] -= z
753                                 p2[2] -= z
754                                 p3[2] -= z
755                                 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
756                                 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
757                                 diff = cursorZ1 - cursorZ0
758                                 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
759                 if not e.Dragging() or self._mouseState != 'tool':
760                         self.tool.OnMouseMove(p0, p1)
761
762                 self._mouseX = e.GetX()
763                 self._mouseY = e.GetY()
764
765         def OnMouseWheel(self, e):
766                 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
767                 delta = max(min(delta,4),-4)
768                 self._zoom *= 1.0 - delta / 10.0
769                 if self._zoom < 1.0:
770                         self._zoom = 1.0
771                 if self._zoom > numpy.max(self._machineSize) * 3:
772                         self._zoom = numpy.max(self._machineSize) * 3
773                 self.Refresh()
774
775         def OnMouseLeave(self, e):
776                 #self._mouseX = -1
777                 pass
778
779         def getMouseRay(self, x, y):
780                 if self._viewport is None:
781                         return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
782                 p0 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
783                 p1 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
784                 p0 -= self._viewTarget
785                 p1 -= self._viewTarget
786                 return p0, p1
787
788         def _init3DView(self):
789                 # set viewing projection
790                 size = self.GetSize()
791                 glViewport(0, 0, size.GetWidth(), size.GetHeight())
792                 glLoadIdentity()
793
794                 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
795
796                 glDisable(GL_RESCALE_NORMAL)
797                 glDisable(GL_LIGHTING)
798                 glDisable(GL_LIGHT0)
799                 glEnable(GL_DEPTH_TEST)
800                 glDisable(GL_CULL_FACE)
801                 glDisable(GL_BLEND)
802                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
803
804                 glClearColor(0.8, 0.8, 0.8, 1.0)
805                 glClearStencil(0)
806                 glClearDepth(1.0)
807
808                 glMatrixMode(GL_PROJECTION)
809                 glLoadIdentity()
810                 aspect = float(size.GetWidth()) / float(size.GetHeight())
811                 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
812
813                 glMatrixMode(GL_MODELVIEW)
814                 glLoadIdentity()
815                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
816
817         def OnPaint(self,e):
818                 if machineCom.machineIsConnected():
819                         self.printButton._imageID = 6
820                         self.printButton._tooltip = _("Print")
821                 elif len(removableStorage.getPossibleSDcardDrives()) > 0:
822                         self.printButton._imageID = 2
823                         self.printButton._tooltip = _("Toolpath to SD")
824                 else:
825                         self.printButton._imageID = 3
826                         self.printButton._tooltip = _("Save toolpath")
827
828                 if self._animView is not None:
829                         self._viewTarget = self._animView.getPosition()
830                         if self._animView.isDone():
831                                 self._animView = None
832                 if self._animZoom is not None:
833                         self._zoom = self._animZoom.getPosition()
834                         if self._animZoom.isDone():
835                                 self._animZoom = None
836                 if self.viewMode == 'gcode' and self._gcode is not None:
837                         try:
838                                 self._viewTarget[2] = self._gcode.layerList[self.layerSelect.getValue()][-1]['points'][0][2]
839                         except:
840                                 pass
841                 if self._objectShader is None:
842                         if opengl.hasShaderSupport():
843                                 self._objectShader = opengl.GLShader("""
844 varying float light_amount;
845
846 void main(void)
847 {
848     gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
849     gl_FrontColor = gl_Color;
850
851         light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
852         light_amount += 0.2;
853 }
854                                 ""","""
855 varying float light_amount;
856
857 void main(void)
858 {
859         gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
860 }
861                                 """)
862                                 self._objectOverhangShader = opengl.GLShader("""
863 uniform float cosAngle;
864 uniform mat3 rotMatrix;
865 varying float light_amount;
866
867 void main(void)
868 {
869     gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
870     gl_FrontColor = gl_Color;
871
872         light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
873         light_amount += 0.2;
874         if (normalize(rotMatrix * gl_Normal).z < -cosAngle)
875         {
876                 light_amount = -10.0;
877         }
878 }
879                                 ""","""
880 varying float light_amount;
881
882 void main(void)
883 {
884         if (light_amount == -10.0)
885         {
886                 gl_FragColor = vec4(1.0, 0.0, 0.0, gl_Color[3]);
887         }else{
888                 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
889         }
890 }
891                                 """)
892                                 self._objectLoadShader = opengl.GLShader("""
893 uniform float intensity;
894 uniform float scale;
895 varying float light_amount;
896
897 void main(void)
898 {
899         vec4 tmp = gl_Vertex;
900     tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
901     tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
902     gl_Position = gl_ModelViewProjectionMatrix * tmp;
903     gl_FrontColor = gl_Color;
904
905         light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
906         light_amount += 0.2;
907 }
908                         ""","""
909 uniform float intensity;
910 varying float light_amount;
911
912 void main(void)
913 {
914         gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
915 }
916                                 """)
917                         if self._objectShader is None or not self._objectShader.isValid():
918                                 self._objectShader = opengl.GLFakeShader()
919                                 self._objectOverhangShader = opengl.GLFakeShader()
920                                 self._objectLoadShader = None
921                 self._init3DView()
922                 glTranslate(0,0,-self._zoom)
923                 glRotate(-self._pitch, 1,0,0)
924                 glRotate(self._yaw, 0,0,1)
925                 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
926
927                 self._viewport = glGetIntegerv(GL_VIEWPORT)
928                 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
929                 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
930
931                 glClearColor(1,1,1,1)
932                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
933
934                 if self.viewMode != 'gcode':
935                         for n in xrange(0, len(self._scene.objects())):
936                                 obj = self._scene.objects()[n]
937                                 glColor4ub((n >> 16) & 0xFF, (n >> 8) & 0xFF, (n >> 0) & 0xFF, 0xFF)
938                                 self._renderObject(obj)
939
940                 if self._mouseX > -1:
941                         glFlush()
942                         n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0] >> 8
943                         if n < len(self._scene.objects()):
944                                 self._focusObj = self._scene.objects()[n]
945                         else:
946                                 self._focusObj = None
947                         f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
948                         #self.GetTopLevelParent().SetTitle(hex(n) + " " + str(f))
949                         self._mouse3Dpos = opengl.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
950                         self._mouse3Dpos -= self._viewTarget
951
952                 self._init3DView()
953                 glTranslate(0,0,-self._zoom)
954                 glRotate(-self._pitch, 1,0,0)
955                 glRotate(self._yaw, 0,0,1)
956                 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
957
958                 if self.viewMode == 'gcode':
959                         if self._gcode is not None and self._gcode.layerList is None:
960                                 self._gcodeLoadThread = threading.Thread(target=self._loadGCode)
961                                 self._gcodeLoadThread.daemon = True
962                                 self._gcodeLoadThread.start()
963                         if self._gcode is not None and self._gcode.layerList is not None:
964                                 glPushMatrix()
965                                 if profile.getMachineSetting('machine_center_is_zero') != 'True':
966                                         glTranslate(-self._machineSize[0] / 2, -self._machineSize[1] / 2, 0)
967                                 t = time.time()
968                                 drawUpTill = min(len(self._gcode.layerList), self.layerSelect.getValue() + 1)
969                                 for n in xrange(0, drawUpTill):
970                                         c = 1.0 - float(drawUpTill - n) / 15
971                                         c = max(0.3, c)
972                                         if len(self._gcodeVBOs) < n + 1:
973                                                 self._gcodeVBOs.append(self._generateGCodeVBOs(self._gcode.layerList[n]))
974                                                 if time.time() - t > 0.5:
975                                                         self.QueueRefresh()
976                                                         break
977                                         #['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']
978                                         if n == drawUpTill - 1:
979                                                 if len(self._gcodeVBOs[n]) < 9:
980                                                         self._gcodeVBOs[n] += self._generateGCodeVBOs2(self._gcode.layerList[n])
981                                                 glColor3f(c, 0, 0)
982                                                 self._gcodeVBOs[n][8].render(GL_QUADS)
983                                                 glColor3f(c/2, 0, c)
984                                                 self._gcodeVBOs[n][9].render(GL_QUADS)
985                                                 glColor3f(0, c, c/2)
986                                                 self._gcodeVBOs[n][10].render(GL_QUADS)
987                                                 glColor3f(c, 0, 0)
988                                                 self._gcodeVBOs[n][11].render(GL_QUADS)
989
990                                                 glColor3f(0, c, 0)
991                                                 self._gcodeVBOs[n][12].render(GL_QUADS)
992                                                 glColor3f(c/2, c/2, 0.0)
993                                                 self._gcodeVBOs[n][13].render(GL_QUADS)
994                                                 glColor3f(0, c, c)
995                                                 self._gcodeVBOs[n][14].render(GL_QUADS)
996                                                 self._gcodeVBOs[n][15].render(GL_QUADS)
997                                                 glColor3f(0, 0, c)
998                                                 self._gcodeVBOs[n][16].render(GL_LINES)
999                                         else:
1000                                                 glColor3f(c, 0, 0)
1001                                                 self._gcodeVBOs[n][0].render(GL_LINES)
1002                                                 glColor3f(c/2, 0, c)
1003                                                 self._gcodeVBOs[n][1].render(GL_LINES)
1004                                                 glColor3f(0, c, c/2)
1005                                                 self._gcodeVBOs[n][2].render(GL_LINES)
1006                                                 glColor3f(c, 0, 0)
1007                                                 self._gcodeVBOs[n][3].render(GL_LINES)
1008
1009                                                 glColor3f(0, c, 0)
1010                                                 self._gcodeVBOs[n][4].render(GL_LINES)
1011                                                 glColor3f(c/2, c/2, 0.0)
1012                                                 self._gcodeVBOs[n][5].render(GL_LINES)
1013                                                 glColor3f(0, c, c)
1014                                                 self._gcodeVBOs[n][6].render(GL_LINES)
1015                                                 self._gcodeVBOs[n][7].render(GL_LINES)
1016                                 glPopMatrix()
1017                 else:
1018                         glStencilFunc(GL_ALWAYS, 1, 1)
1019                         glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1020
1021                         if self.viewMode == 'overhang':
1022                                 self._objectOverhangShader.bind()
1023                                 self._objectOverhangShader.setUniform('cosAngle', math.cos(math.radians(90 - 60)))
1024                         else:
1025                                 self._objectShader.bind()
1026                         for obj in self._scene.objects():
1027                                 if obj._loadAnim is not None:
1028                                         if obj._loadAnim.isDone():
1029                                                 obj._loadAnim = None
1030                                         else:
1031                                                 continue
1032                                 brightness = 1.0
1033                                 if self._focusObj == obj:
1034                                         brightness = 1.2
1035                                 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
1036                                         brightness = 0.8
1037
1038                                 if self._selectedObj == obj or self._selectedObj is None:
1039                                         #If we want transparent, then first render a solid black model to remove the printer size lines.
1040                                         if self.viewMode == 'transparent':
1041                                                 glColor4f(0, 0, 0, 0)
1042                                                 self._renderObject(obj)
1043                                                 glEnable(GL_BLEND)
1044                                                 glBlendFunc(GL_ONE, GL_ONE)
1045                                                 glDisable(GL_DEPTH_TEST)
1046                                                 brightness *= 0.5
1047                                         if self.viewMode == 'xray':
1048                                                 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
1049                                         glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1050                                         glEnable(GL_STENCIL_TEST)
1051
1052                                 if self.viewMode == 'overhang':
1053                                         if self._selectedObj == obj and self.tempMatrix is not None:
1054                                                 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix() * self.tempMatrix)
1055                                         else:
1056                                                 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix())
1057
1058                                 if not self._scene.checkPlatform(obj):
1059                                         glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
1060                                         self._renderObject(obj)
1061                                 else:
1062                                         self._renderObject(obj, brightness)
1063                                 glDisable(GL_STENCIL_TEST)
1064                                 glDisable(GL_BLEND)
1065                                 glEnable(GL_DEPTH_TEST)
1066                                 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
1067
1068                         if self.viewMode == 'xray':
1069                                 glPushMatrix()
1070                                 glLoadIdentity()
1071                                 glEnable(GL_STENCIL_TEST)
1072                                 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
1073                                 glDisable(GL_DEPTH_TEST)
1074                                 for i in xrange(2, 15, 2):
1075                                         glStencilFunc(GL_EQUAL, i, 0xFF)
1076                                         glColor(float(i)/10, float(i)/10, float(i)/5)
1077                                         glBegin(GL_QUADS)
1078                                         glVertex3f(-1000,-1000,-10)
1079                                         glVertex3f( 1000,-1000,-10)
1080                                         glVertex3f( 1000, 1000,-10)
1081                                         glVertex3f(-1000, 1000,-10)
1082                                         glEnd()
1083                                 for i in xrange(1, 15, 2):
1084                                         glStencilFunc(GL_EQUAL, i, 0xFF)
1085                                         glColor(float(i)/10, 0, 0)
1086                                         glBegin(GL_QUADS)
1087                                         glVertex3f(-1000,-1000,-10)
1088                                         glVertex3f( 1000,-1000,-10)
1089                                         glVertex3f( 1000, 1000,-10)
1090                                         glVertex3f(-1000, 1000,-10)
1091                                         glEnd()
1092                                 glPopMatrix()
1093                                 glDisable(GL_STENCIL_TEST)
1094                                 glEnable(GL_DEPTH_TEST)
1095
1096                         self._objectShader.unbind()
1097
1098                         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1099                         glEnable(GL_BLEND)
1100                         if self._objectLoadShader is not None:
1101                                 self._objectLoadShader.bind()
1102                                 glColor4f(0.2, 0.6, 1.0, 1.0)
1103                                 for obj in self._scene.objects():
1104                                         if obj._loadAnim is None:
1105                                                 continue
1106                                         self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
1107                                         self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
1108                                         self._renderObject(obj)
1109                                 self._objectLoadShader.unbind()
1110                                 glDisable(GL_BLEND)
1111
1112                 self._drawMachine()
1113
1114                 if self._usbPrintMonitor.getState() == 'PRINTING' and self._usbPrintMonitor.getID() == self._slicer.getID():
1115                         glEnable(GL_BLEND)
1116                         z = self._usbPrintMonitor.getZ()
1117                         size = self._machineSize
1118                         glColor4ub(255,255,0,128)
1119                         glBegin(GL_QUADS)
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                         glVertex3f(-size[0]/2, size[1]/2, z)
1124                         glEnd()
1125
1126                 if self.viewMode == 'gcode':
1127                         if self._gcodeLoadThread is not None and self._gcodeLoadThread.isAlive():
1128                                 glDisable(GL_DEPTH_TEST)
1129                                 glPushMatrix()
1130                                 glLoadIdentity()
1131                                 glTranslate(0,-4,-10)
1132                                 glColor4ub(60,60,60,255)
1133                                 opengl.glDrawStringCenter(_("Loading toolpath for visualization..."))
1134                                 glPopMatrix()
1135                 else:
1136                         #Draw the object box-shadow, so you can see where it will collide with other objects.
1137                         if self._selectedObj is not None and len(self._scene.objects()) > 1:
1138                                 size = self._selectedObj.getSize()[0:2] / 2 + self._scene.getObjectExtend()
1139                                 glPushMatrix()
1140                                 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0)
1141                                 glEnable(GL_BLEND)
1142                                 glEnable(GL_CULL_FACE)
1143                                 glColor4f(0,0,0,0.12)
1144                                 glBegin(GL_QUADS)
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                                 glVertex3f( size[0],  size[1], 0.1)
1149                                 glEnd()
1150                                 glDisable(GL_CULL_FACE)
1151                                 glPopMatrix()
1152
1153                         #Draw the outline of the selected object, on top of everything else except the GUI.
1154                         if self._selectedObj is not None and self._selectedObj._loadAnim is None:
1155                                 glDisable(GL_DEPTH_TEST)
1156                                 glEnable(GL_CULL_FACE)
1157                                 glEnable(GL_STENCIL_TEST)
1158                                 glDisable(GL_BLEND)
1159                                 glStencilFunc(GL_EQUAL, 0, 255)
1160
1161                                 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
1162                                 glLineWidth(2)
1163                                 glColor4f(1,1,1,0.5)
1164                                 self._renderObject(self._selectedObj)
1165                                 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
1166
1167                                 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
1168                                 glDisable(GL_STENCIL_TEST)
1169                                 glDisable(GL_CULL_FACE)
1170                                 glEnable(GL_DEPTH_TEST)
1171
1172                         if self._selectedObj is not None:
1173                                 glPushMatrix()
1174                                 pos = self.getObjectCenterPos()
1175                                 glTranslate(pos[0], pos[1], pos[2])
1176                                 self.tool.OnDraw()
1177                                 glPopMatrix()
1178                 if self.viewMode == 'overhang' and not opengl.hasShaderSupport():
1179                         glDisable(GL_DEPTH_TEST)
1180                         glPushMatrix()
1181                         glLoadIdentity()
1182                         glTranslate(0,-4,-10)
1183                         glColor4ub(60,60,60,255)
1184                         opengl.glDrawStringCenter(_("Overhang view not working due to lack of OpenGL shaders support."))
1185                         glPopMatrix()
1186
1187         def _renderObject(self, obj, brightness = False, addSink = True):
1188                 glPushMatrix()
1189                 if addSink:
1190                         glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2 - profile.getProfileSettingFloat('object_sink'))
1191                 else:
1192                         glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
1193
1194                 if self.tempMatrix is not None and obj == self._selectedObj:
1195                         tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
1196                         glMultMatrixf(tempMatrix)
1197
1198                 offset = obj.getDrawOffset()
1199                 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
1200
1201                 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
1202                 glMultMatrixf(tempMatrix)
1203
1204                 n = 0
1205                 for m in obj._meshList:
1206                         if m.vbo is None:
1207                                 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
1208                         if brightness:
1209                                 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
1210                                 n += 1
1211                         m.vbo.render()
1212                 glPopMatrix()
1213
1214         def _drawMachine(self):
1215                 glEnable(GL_CULL_FACE)
1216                 glEnable(GL_BLEND)
1217
1218                 size = [profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')]
1219
1220                 machine = profile.getMachineSetting('machine_type')
1221                 if profile.getMachineSetting('machine_type').startswith('ultimaker'):
1222                         if machine not in self._platformMesh:
1223                                 meshes = meshLoader.loadMeshes(resources.getPathForMesh(machine + '_platform.stl'))
1224                                 if len(meshes) > 0:
1225                                         self._platformMesh[machine] = meshes[0]
1226                                 else:
1227                                         self._platformMesh[machine] = None
1228                                 if machine == 'ultimaker2':
1229                                         self._platformMesh[machine]._drawOffset = numpy.array([0,-37,145], numpy.float32)
1230                                 else:
1231                                         self._platformMesh[machine]._drawOffset = numpy.array([0,0,2.5], numpy.float32)
1232                         glColor4f(1,1,1,0.5)
1233                         self._objectShader.bind()
1234                         self._renderObject(self._platformMesh[machine], False, False)
1235                         self._objectShader.unbind()
1236
1237                         #For the Ultimaker 2 render the texture on the back plate to show the Ultimaker2 text.
1238                         if machine == 'ultimaker2':
1239                                 if not hasattr(self._platformMesh[machine], 'texture'):
1240                                         self._platformMesh[machine].texture = opengl.loadGLTexture('Ultimaker2backplate.png')
1241                                 glBindTexture(GL_TEXTURE_2D, self._platformMesh[machine].texture)
1242                                 glEnable(GL_TEXTURE_2D)
1243                                 glPushMatrix()
1244                                 glColor4f(1,1,1,1)
1245
1246                                 glTranslate(0,150,-5)
1247                                 h = 50
1248                                 d = 8
1249                                 w = 100
1250                                 glEnable(GL_BLEND)
1251                                 glBlendFunc(GL_DST_COLOR, GL_ZERO)
1252                                 glBegin(GL_QUADS)
1253                                 glTexCoord2f(1, 0)
1254                                 glVertex3f( w, 0, h)
1255                                 glTexCoord2f(0, 0)
1256                                 glVertex3f(-w, 0, h)
1257                                 glTexCoord2f(0, 1)
1258                                 glVertex3f(-w, 0, 0)
1259                                 glTexCoord2f(1, 1)
1260                                 glVertex3f( w, 0, 0)
1261
1262                                 glTexCoord2f(1, 0)
1263                                 glVertex3f(-w, d, h)
1264                                 glTexCoord2f(0, 0)
1265                                 glVertex3f( w, d, h)
1266                                 glTexCoord2f(0, 1)
1267                                 glVertex3f( w, d, 0)
1268                                 glTexCoord2f(1, 1)
1269                                 glVertex3f(-w, d, 0)
1270                                 glEnd()
1271                                 glDisable(GL_TEXTURE_2D)
1272                                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1273                                 glPopMatrix()
1274                 else:
1275                         glColor4f(0,0,0,1)
1276                         glLineWidth(3)
1277                         glBegin(GL_LINES)
1278                         glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1279                         glVertex3f(-size[0] / 2, -size[1] / 2, 10)
1280                         glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1281                         glVertex3f(-size[0] / 2+10, -size[1] / 2, 0)
1282                         glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1283                         glVertex3f(-size[0] / 2, -size[1] / 2+10, 0)
1284                         glEnd()
1285
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                 sx = self._machineSize[0]
1308                 sy = self._machineSize[1]
1309                 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
1310                         for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
1311                                 x1 = x * 10
1312                                 x2 = x1 + 10
1313                                 y1 = y * 10
1314                                 y2 = y1 + 10
1315                                 x1 = max(min(x1, sx/2), -sx/2)
1316                                 y1 = max(min(y1, sy/2), -sy/2)
1317                                 x2 = max(min(x2, sx/2), -sx/2)
1318                                 y2 = max(min(y2, sy/2), -sy/2)
1319                                 if (x & 1) == (y & 1):
1320                                         glColor4ub(5, 171, 231, 127)
1321                                 else:
1322                                         glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
1323                                 glBegin(GL_QUADS)
1324                                 glVertex3f(x1, y1, -0.02)
1325                                 glVertex3f(x2, y1, -0.02)
1326                                 glVertex3f(x2, y2, -0.02)
1327                                 glVertex3f(x1, y2, -0.02)
1328                                 glEnd()
1329
1330                 glDisable(GL_BLEND)
1331                 glDisable(GL_CULL_FACE)
1332
1333         def _generateGCodeVBOs(self, layer):
1334                 ret = []
1335                 for extrudeType in ['WALL-OUTER:0', 'WALL-OUTER:1', 'WALL-OUTER:2', 'WALL-OUTER:3', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1336                         if ':' in extrudeType:
1337                                 extruder = int(extrudeType[extrudeType.find(':')+1:])
1338                                 extrudeType = extrudeType[0:extrudeType.find(':')]
1339                         else:
1340                                 extruder = None
1341                         pointList = numpy.zeros((0,3), numpy.float32)
1342                         for path in layer:
1343                                 if path['type'] == 'extrude' and path['pathType'] == extrudeType and (extruder is None or path['extruder'] == extruder):
1344                                         a = path['points']
1345                                         a = numpy.concatenate((a[:-1], a[1:]), 1)
1346                                         a = a.reshape((len(a) * 2, 3))
1347                                         pointList = numpy.concatenate((pointList, a))
1348                         ret.append(opengl.GLVBO(pointList))
1349                 return ret
1350
1351         def _generateGCodeVBOs2(self, layer):
1352                 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
1353                 filamentArea = math.pi * filamentRadius * filamentRadius
1354                 useFilamentArea = profile.getMachineSetting('gcode_flavor') == 'UltiGCode'
1355
1356                 ret = []
1357                 for extrudeType in ['WALL-OUTER:0', 'WALL-OUTER:1', 'WALL-OUTER:2', 'WALL-OUTER:3', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1358                         if ':' in extrudeType:
1359                                 extruder = int(extrudeType[extrudeType.find(':')+1:])
1360                                 extrudeType = extrudeType[0:extrudeType.find(':')]
1361                         else:
1362                                 extruder = None
1363                         pointList = numpy.zeros((0,3), numpy.float32)
1364                         for path in layer:
1365                                 if path['type'] == 'extrude' and path['pathType'] == extrudeType and (extruder is None or path['extruder'] == extruder):
1366                                         a = path['points']
1367                                         if extrudeType == 'FILL':
1368                                                 a[:,2] += 0.01
1369
1370                                         normal = a[1:] - a[:-1]
1371                                         lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
1372                                         normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
1373                                         normal[:,2] /= lens
1374
1375                                         ePerDist = path['extrusion'][1:] / lens
1376                                         if useFilamentArea:
1377                                                 lineWidth = ePerDist / path['layerThickness'] / 2.0
1378                                         else:
1379                                                 lineWidth = ePerDist * (filamentArea / path['layerThickness'] / 2)
1380
1381                                         normal[:,0] *= lineWidth
1382                                         normal[:,1] *= lineWidth
1383
1384                                         b = numpy.zeros((len(a)-1, 0), numpy.float32)
1385                                         b = numpy.concatenate((b, a[1:] + normal), 1)
1386                                         b = numpy.concatenate((b, a[1:] - normal), 1)
1387                                         b = numpy.concatenate((b, a[:-1] - normal), 1)
1388                                         b = numpy.concatenate((b, a[:-1] + normal), 1)
1389                                         b = b.reshape((len(b) * 4, 3))
1390
1391                                         if len(a) > 2:
1392                                                 normal2 = normal[:-1] + normal[1:]
1393                                                 lens2 = numpy.sqrt(normal2[:,0]**2 + normal2[:,1]**2)
1394                                                 normal2[:,0] /= lens2
1395                                                 normal2[:,1] /= lens2
1396                                                 normal2[:,0] *= lineWidth[:-1]
1397                                                 normal2[:,1] *= lineWidth[:-1]
1398
1399                                                 c = numpy.zeros((len(a)-2, 0), numpy.float32)
1400                                                 c = numpy.concatenate((c, a[1:-1]), 1)
1401                                                 c = numpy.concatenate((c, a[1:-1]+normal[1:]), 1)
1402                                                 c = numpy.concatenate((c, a[1:-1]+normal2), 1)
1403                                                 c = numpy.concatenate((c, a[1:-1]+normal[:-1]), 1)
1404
1405                                                 c = numpy.concatenate((c, a[1:-1]), 1)
1406                                                 c = numpy.concatenate((c, a[1:-1]-normal[1:]), 1)
1407                                                 c = numpy.concatenate((c, a[1:-1]-normal2), 1)
1408                                                 c = numpy.concatenate((c, a[1:-1]-normal[:-1]), 1)
1409
1410                                                 c = c.reshape((len(c) * 8, 3))
1411
1412                                                 pointList = numpy.concatenate((pointList, b, c))
1413                                         else:
1414                                                 pointList = numpy.concatenate((pointList, b))
1415                         ret.append(opengl.GLVBO(pointList))
1416
1417                 pointList = numpy.zeros((0,3), numpy.float32)
1418                 for path in layer:
1419                         if path['type'] == 'move':
1420                                 a = path['points'] + numpy.array([0,0,0.01], numpy.float32)
1421                                 a = numpy.concatenate((a[:-1], a[1:]), 1)
1422                                 a = a.reshape((len(a) * 2, 3))
1423                                 pointList = numpy.concatenate((pointList, a))
1424                         if path['type'] == 'retract':
1425                                 a = path['points'] + numpy.array([0,0,0.01], numpy.float32)
1426                                 a = numpy.concatenate((a[:-1], a[1:] + numpy.array([0,0,1], numpy.float32)), 1)
1427                                 a = a.reshape((len(a) * 2, 3))
1428                                 pointList = numpy.concatenate((pointList, a))
1429                 ret.append(opengl.GLVBO(pointList))
1430
1431                 return ret
1432
1433         def getObjectCenterPos(self):
1434                 if self._selectedObj is None:
1435                         return [0.0, 0.0, 0.0]
1436                 pos = self._selectedObj.getPosition()
1437                 size = self._selectedObj.getSize()
1438                 return [pos[0], pos[1], size[2]/2 - profile.getProfileSettingFloat('object_sink')]
1439
1440         def getObjectBoundaryCircle(self):
1441                 if self._selectedObj is None:
1442                         return 0.0
1443                 return self._selectedObj.getBoundaryCircle()
1444
1445         def getObjectSize(self):
1446                 if self._selectedObj is None:
1447                         return [0.0, 0.0, 0.0]
1448                 return self._selectedObj.getSize()
1449
1450         def getObjectMatrix(self):
1451                 if self._selectedObj is None:
1452                         return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1453                 return self._selectedObj.getMatrix()
1454
1455 class shaderEditor(wx.Dialog):
1456         def __init__(self, parent, callback, v, f):
1457                 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1458                 self._callback = callback
1459                 s = wx.BoxSizer(wx.VERTICAL)
1460                 self.SetSizer(s)
1461                 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1462                 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1463                 s.Add(self._vertex, 1, flag=wx.EXPAND)
1464                 s.Add(self._fragment, 1, flag=wx.EXPAND)
1465
1466                 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1467                 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1468
1469                 self.SetPosition(self.GetParent().GetPosition())
1470                 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1471                 self.Show()
1472
1473         def OnText(self, e):
1474                 self._callback(self._vertex.GetValue(), self._fragment.GetValue())