chiark / gitweb /
Reslice after centering.
[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                         cost = self._slicer.getFilamentCost()
510                         if cost is not None:
511                                 self.printButton.setBottomText('%s\n%s\n%s' % (self._slicer.getPrintTime(), self._slicer.getFilamentAmount(), cost))
512                         else:
513                                 self.printButton.setBottomText('%s\n%s' % (self._slicer.getPrintTime(), self._slicer.getFilamentAmount()))
514                         self._gcode = gcodeInterpreter.gcode()
515                         self._gcodeFilename = self._slicer.getGCodeFilename()
516                 else:
517                         self.printButton.setBottomText('')
518                 self.QueueRefresh()
519
520         def _loadGCode(self):
521                 self._gcode.progressCallback = self._gcodeLoadCallback
522                 self._gcode.load(self._gcodeFilename)
523
524         def _gcodeLoadCallback(self, progress):
525                 if self._gcode is None:
526                         return True
527                 if len(self._gcode.layerList) % 15 == 0:
528                         time.sleep(0.1)
529                 if self._gcode is None:
530                         return True
531                 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
532                 if self.viewMode == 'gcode':
533                         self._queueRefresh()
534                 return False
535
536         def loadScene(self, fileList):
537                 for filename in fileList:
538                         try:
539                                 ext = os.path.splitext(filename)[1].lower()
540                                 if ext in imageToMesh.supportedExtensions():
541                                         imageToMesh.convertImageDialog(self, filename).Show()
542                                         objList = []
543                                 else:
544                                         objList = meshLoader.loadMeshes(filename)
545                         except:
546                                 traceback.print_exc()
547                         else:
548                                 for obj in objList:
549                                         if self._objectLoadShader is not None:
550                                                 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
551                                         else:
552                                                 obj._loadAnim = None
553                                         self._scene.add(obj)
554                                         self._scene.centerAll()
555                                         self._selectObject(obj)
556                                         if obj.getScale()[0] < 1.0:
557                                                 self.notification.message("Warning: Object scaled down.")
558                 self.sceneUpdated()
559
560         def _deleteObject(self, obj):
561                 if obj == self._selectedObj:
562                         self._selectObject(None)
563                 if obj == self._focusObj:
564                         self._focusObj = None
565                 self._scene.remove(obj)
566                 for m in obj._meshList:
567                         if m.vbo is not None and m.vbo.decRef():
568                                 self.glReleaseList.append(m.vbo)
569                 import gc
570                 gc.collect()
571                 self.sceneUpdated()
572
573         def _selectObject(self, obj, zoom = True):
574                 if obj != self._selectedObj:
575                         self._selectedObj = obj
576                         self.updateProfileToControls()
577                         self.updateToolButtons()
578                 if zoom and obj is not None:
579                         newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2])
580                         self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
581                         newZoom = obj.getBoundaryCircle() * 6
582                         if newZoom > numpy.max(self._machineSize) * 3:
583                                 newZoom = numpy.max(self._machineSize) * 3
584                         self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
585
586         def updateProfileToControls(self):
587                 oldSimpleMode = self._isSimpleMode
588                 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
589                 if self._isSimpleMode != oldSimpleMode:
590                         self._scene.arrangeAll()
591                         self.sceneUpdated()
592                 self._machineSize = numpy.array([profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')])
593                 self._objColors[0] = profile.getPreferenceColour('model_colour')
594                 self._objColors[1] = profile.getPreferenceColour('model_colour2')
595                 self._objColors[2] = profile.getPreferenceColour('model_colour3')
596                 self._objColors[3] = profile.getPreferenceColour('model_colour4')
597                 self._scene.setMachineSize(self._machineSize)
598                 self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
599                 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'))
600
601                 if self._selectedObj is not None:
602                         scale = self._selectedObj.getScale()
603                         size = self._selectedObj.getSize()
604                         self.scaleXctrl.setValue(round(scale[0], 2))
605                         self.scaleYctrl.setValue(round(scale[1], 2))
606                         self.scaleZctrl.setValue(round(scale[2], 2))
607                         self.scaleXmmctrl.setValue(round(size[0], 2))
608                         self.scaleYmmctrl.setValue(round(size[1], 2))
609                         self.scaleZmmctrl.setValue(round(size[2], 2))
610
611         def OnKeyChar(self, keyCode):
612                 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE or (keyCode == wx.WXK_BACK and platform.system() == "Darwin"):
613                         if self._selectedObj is not None:
614                                 self._deleteObject(self._selectedObj)
615                                 self.QueueRefresh()
616                 if keyCode == wx.WXK_UP:
617                         self.layerSelect.setValue(self.layerSelect.getValue() + 1)
618                         self.QueueRefresh()
619                 elif keyCode == wx.WXK_DOWN:
620                         self.layerSelect.setValue(self.layerSelect.getValue() - 1)
621                         self.QueueRefresh()
622                 elif keyCode == wx.WXK_PAGEUP:
623                         self.layerSelect.setValue(self.layerSelect.getValue() + 10)
624                         self.QueueRefresh()
625                 elif keyCode == wx.WXK_PAGEDOWN:
626                         self.layerSelect.setValue(self.layerSelect.getValue() - 10)
627                         self.QueueRefresh()
628
629                 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
630                         shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
631                 if keyCode == wx.WXK_F4 and wx.GetKeyState(wx.WXK_SHIFT):
632                         from collections import defaultdict
633                         from gc import get_objects
634                         self._beforeLeakTest = defaultdict(int)
635                         for i in get_objects():
636                                 self._beforeLeakTest[type(i)] += 1
637                 if keyCode == wx.WXK_F5 and wx.GetKeyState(wx.WXK_SHIFT):
638                         from collections import defaultdict
639                         from gc import get_objects
640                         self._afterLeakTest = defaultdict(int)
641                         for i in get_objects():
642                                 self._afterLeakTest[type(i)] += 1
643                         for k in self._afterLeakTest:
644                                 if self._afterLeakTest[k]-self._beforeLeakTest[k]:
645                                         print k, self._afterLeakTest[k], self._beforeLeakTest[k], self._afterLeakTest[k] - self._beforeLeakTest[k]
646
647         def ShaderUpdate(self, v, f):
648                 s = opengl.GLShader(v, f)
649                 if s.isValid():
650                         self._objectLoadShader.release()
651                         self._objectLoadShader = s
652                         for obj in self._scene.objects():
653                                 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
654                         self.QueueRefresh()
655
656         def OnMouseDown(self,e):
657                 self._mouseX = e.GetX()
658                 self._mouseY = e.GetY()
659                 self._mouseClick3DPos = self._mouse3Dpos
660                 self._mouseClickFocus = self._focusObj
661                 if e.ButtonDClick():
662                         self._mouseState = 'doubleClick'
663                 else:
664                         self._mouseState = 'dragOrClick'
665                 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
666                 p0 -= self.getObjectCenterPos() - self._viewTarget
667                 p1 -= self.getObjectCenterPos() - self._viewTarget
668                 if self.tool.OnDragStart(p0, p1):
669                         self._mouseState = 'tool'
670                 if self._mouseState == 'dragOrClick':
671                         if e.GetButton() == 1:
672                                 if self._focusObj is not None:
673                                         self._selectObject(self._focusObj, False)
674                                         self.QueueRefresh()
675
676         def OnMouseUp(self, e):
677                 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
678                         return
679                 if self._mouseState == 'dragOrClick':
680                         if e.GetButton() == 1:
681                                 self._selectObject(self._focusObj)
682                         if e.GetButton() == 3:
683                                         menu = wx.Menu()
684                                         if self._focusObj is not None:
685                                                 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, _("Delete object")))
686                                                 self.Bind(wx.EVT_MENU, self.OnCenter, menu.Append(-1, _("Center on platform")))
687                                                 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, _("Multiply object")))
688                                                 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, _("Split object into parts")))
689                                         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:
690                                                 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, _("Dual extrusion merge")))
691                                         if len(self._scene.objects()) > 0:
692                                                 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, _("Delete all objects")))
693                                         if menu.MenuItemCount > 0:
694                                                 self.PopupMenu(menu)
695                                         menu.Destroy()
696                 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
697                         self._scene.pushFree()
698                         self.sceneUpdated()
699                 elif self._mouseState == 'tool':
700                         if self.tempMatrix is not None and self._selectedObj is not None:
701                                 self._selectedObj.applyMatrix(self.tempMatrix)
702                                 self._scene.pushFree()
703                                 self._selectObject(self._selectedObj)
704                         self.tempMatrix = None
705                         self.tool.OnDragEnd()
706                         self.sceneUpdated()
707                 self._mouseState = None
708
709         def OnMouseMotion(self,e):
710                 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
711                 p0 -= self.getObjectCenterPos() - self._viewTarget
712                 p1 -= self.getObjectCenterPos() - self._viewTarget
713
714                 if e.Dragging() and self._mouseState is not None:
715                         if self._mouseState == 'tool':
716                                 self.tool.OnDrag(p0, p1)
717                         elif not e.LeftIsDown() and e.RightIsDown():
718                                 self._mouseState = 'drag'
719                                 if wx.GetKeyState(wx.WXK_SHIFT):
720                                         a = math.cos(math.radians(self._yaw)) / 3.0
721                                         b = math.sin(math.radians(self._yaw)) / 3.0
722                                         self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
723                                         self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
724                                         self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
725                                         self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
726                                 else:
727                                         self._yaw += e.GetX() - self._mouseX
728                                         self._pitch -= e.GetY() - self._mouseY
729                                 if self._pitch > 170:
730                                         self._pitch = 170
731                                 if self._pitch < 10:
732                                         self._pitch = 10
733                         elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
734                                 self._mouseState = 'drag'
735                                 self._zoom += e.GetY() - self._mouseY
736                                 if self._zoom < 1:
737                                         self._zoom = 1
738                                 if self._zoom > numpy.max(self._machineSize) * 3:
739                                         self._zoom = numpy.max(self._machineSize) * 3
740                         elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus:
741                                 self._mouseState = 'dragObject'
742                                 z = max(0, self._mouseClick3DPos[2])
743                                 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
744                                 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
745                                 p0[2] -= z
746                                 p1[2] -= z
747                                 p2[2] -= z
748                                 p3[2] -= z
749                                 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
750                                 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
751                                 diff = cursorZ1 - cursorZ0
752                                 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
753                 if not e.Dragging() or self._mouseState != 'tool':
754                         self.tool.OnMouseMove(p0, p1)
755
756                 self._mouseX = e.GetX()
757                 self._mouseY = e.GetY()
758
759         def OnMouseWheel(self, e):
760                 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
761                 delta = max(min(delta,4),-4)
762                 self._zoom *= 1.0 - delta / 10.0
763                 if self._zoom < 1.0:
764                         self._zoom = 1.0
765                 if self._zoom > numpy.max(self._machineSize) * 3:
766                         self._zoom = numpy.max(self._machineSize) * 3
767                 self.Refresh()
768
769         def OnMouseLeave(self, e):
770                 #self._mouseX = -1
771                 pass
772
773         def getMouseRay(self, x, y):
774                 if self._viewport is None:
775                         return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
776                 p0 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
777                 p1 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
778                 p0 -= self._viewTarget
779                 p1 -= self._viewTarget
780                 return p0, p1
781
782         def _init3DView(self):
783                 # set viewing projection
784                 size = self.GetSize()
785                 glViewport(0, 0, size.GetWidth(), size.GetHeight())
786                 glLoadIdentity()
787
788                 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
789
790                 glDisable(GL_RESCALE_NORMAL)
791                 glDisable(GL_LIGHTING)
792                 glDisable(GL_LIGHT0)
793                 glEnable(GL_DEPTH_TEST)
794                 glDisable(GL_CULL_FACE)
795                 glDisable(GL_BLEND)
796                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
797
798                 glClearColor(0.8, 0.8, 0.8, 1.0)
799                 glClearStencil(0)
800                 glClearDepth(1.0)
801
802                 glMatrixMode(GL_PROJECTION)
803                 glLoadIdentity()
804                 aspect = float(size.GetWidth()) / float(size.GetHeight())
805                 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
806
807                 glMatrixMode(GL_MODELVIEW)
808                 glLoadIdentity()
809                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
810
811         def OnPaint(self,e):
812                 if machineCom.machineIsConnected():
813                         self.printButton._imageID = 6
814                         self.printButton._tooltip = _("Print")
815                 elif len(removableStorage.getPossibleSDcardDrives()) > 0:
816                         self.printButton._imageID = 2
817                         self.printButton._tooltip = _("Toolpath to SD")
818                 else:
819                         self.printButton._imageID = 3
820                         self.printButton._tooltip = _("Save toolpath")
821
822                 if self._animView is not None:
823                         self._viewTarget = self._animView.getPosition()
824                         if self._animView.isDone():
825                                 self._animView = None
826                 if self._animZoom is not None:
827                         self._zoom = self._animZoom.getPosition()
828                         if self._animZoom.isDone():
829                                 self._animZoom = None
830                 if self.viewMode == 'gcode' and self._gcode is not None:
831                         try:
832                                 self._viewTarget[2] = self._gcode.layerList[self.layerSelect.getValue()][-1]['points'][0][2]
833                         except:
834                                 pass
835                 if self._objectShader is None:
836                         if opengl.hasShaderSupport():
837                                 self._objectShader = opengl.GLShader("""
838 varying float light_amount;
839
840 void main(void)
841 {
842     gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
843     gl_FrontColor = gl_Color;
844
845         light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
846         light_amount += 0.2;
847 }
848                                 ""","""
849 varying float light_amount;
850
851 void main(void)
852 {
853         gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
854 }
855                                 """)
856                                 self._objectOverhangShader = opengl.GLShader("""
857 uniform float cosAngle;
858 uniform mat3 rotMatrix;
859 varying float light_amount;
860
861 void main(void)
862 {
863     gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
864     gl_FrontColor = gl_Color;
865
866         light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
867         light_amount += 0.2;
868         if (normalize(rotMatrix * gl_Normal).z < -cosAngle)
869         {
870                 light_amount = -10.0;
871         }
872 }
873                                 ""","""
874 varying float light_amount;
875
876 void main(void)
877 {
878         if (light_amount == -10.0)
879         {
880                 gl_FragColor = vec4(1.0, 0.0, 0.0, gl_Color[3]);
881         }else{
882                 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
883         }
884 }
885                                 """)
886                                 self._objectLoadShader = opengl.GLShader("""
887 uniform float intensity;
888 uniform float scale;
889 varying float light_amount;
890
891 void main(void)
892 {
893         vec4 tmp = gl_Vertex;
894     tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
895     tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
896     gl_Position = gl_ModelViewProjectionMatrix * tmp;
897     gl_FrontColor = gl_Color;
898
899         light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
900         light_amount += 0.2;
901 }
902                         ""","""
903 uniform float intensity;
904 varying float light_amount;
905
906 void main(void)
907 {
908         gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
909 }
910                                 """)
911                         if self._objectShader is None or not self._objectShader.isValid():
912                                 self._objectShader = opengl.GLFakeShader()
913                                 self._objectOverhangShader = opengl.GLFakeShader()
914                                 self._objectLoadShader = None
915                 self._init3DView()
916                 glTranslate(0,0,-self._zoom)
917                 glRotate(-self._pitch, 1,0,0)
918                 glRotate(self._yaw, 0,0,1)
919                 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
920
921                 self._viewport = glGetIntegerv(GL_VIEWPORT)
922                 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
923                 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
924
925                 glClearColor(1,1,1,1)
926                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
927
928                 if self.viewMode != 'gcode':
929                         for n in xrange(0, len(self._scene.objects())):
930                                 obj = self._scene.objects()[n]
931                                 glColor4ub((n >> 16) & 0xFF, (n >> 8) & 0xFF, (n >> 0) & 0xFF, 0xFF)
932                                 self._renderObject(obj)
933
934                 if self._mouseX > -1:
935                         glFlush()
936                         n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0] >> 8
937                         if n < len(self._scene.objects()):
938                                 self._focusObj = self._scene.objects()[n]
939                         else:
940                                 self._focusObj = None
941                         f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
942                         #self.GetTopLevelParent().SetTitle(hex(n) + " " + str(f))
943                         self._mouse3Dpos = opengl.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
944                         self._mouse3Dpos -= self._viewTarget
945
946                 self._init3DView()
947                 glTranslate(0,0,-self._zoom)
948                 glRotate(-self._pitch, 1,0,0)
949                 glRotate(self._yaw, 0,0,1)
950                 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
951
952                 if self.viewMode == 'gcode':
953                         if self._gcode is not None and self._gcode.layerList is None:
954                                 self._gcodeLoadThread = threading.Thread(target=self._loadGCode)
955                                 self._gcodeLoadThread.daemon = True
956                                 self._gcodeLoadThread.start()
957                         if self._gcode is not None and self._gcode.layerList is not None:
958                                 glPushMatrix()
959                                 if profile.getMachineSetting('machine_center_is_zero') != 'True':
960                                         glTranslate(-self._machineSize[0] / 2, -self._machineSize[1] / 2, 0)
961                                 t = time.time()
962                                 drawUpTill = min(len(self._gcode.layerList), self.layerSelect.getValue() + 1)
963                                 for n in xrange(0, drawUpTill):
964                                         c = 1.0 - float(drawUpTill - n) / 15
965                                         c = max(0.3, c)
966                                         if len(self._gcodeVBOs) < n + 1:
967                                                 self._gcodeVBOs.append(self._generateGCodeVBOs(self._gcode.layerList[n]))
968                                                 if time.time() - t > 0.5:
969                                                         self.QueueRefresh()
970                                                         break
971                                         #['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']
972                                         if n == drawUpTill - 1:
973                                                 if len(self._gcodeVBOs[n]) < 9:
974                                                         self._gcodeVBOs[n] += self._generateGCodeVBOs2(self._gcode.layerList[n])
975                                                 glColor3f(c, 0, 0)
976                                                 self._gcodeVBOs[n][8].render(GL_QUADS)
977                                                 glColor3f(c/2, 0, c)
978                                                 self._gcodeVBOs[n][9].render(GL_QUADS)
979                                                 glColor3f(0, c, c/2)
980                                                 self._gcodeVBOs[n][10].render(GL_QUADS)
981                                                 glColor3f(c, 0, 0)
982                                                 self._gcodeVBOs[n][11].render(GL_QUADS)
983
984                                                 glColor3f(0, c, 0)
985                                                 self._gcodeVBOs[n][12].render(GL_QUADS)
986                                                 glColor3f(c/2, c/2, 0.0)
987                                                 self._gcodeVBOs[n][13].render(GL_QUADS)
988                                                 glColor3f(0, c, c)
989                                                 self._gcodeVBOs[n][14].render(GL_QUADS)
990                                                 self._gcodeVBOs[n][15].render(GL_QUADS)
991                                                 glColor3f(0, 0, c)
992                                                 self._gcodeVBOs[n][16].render(GL_LINES)
993                                         else:
994                                                 glColor3f(c, 0, 0)
995                                                 self._gcodeVBOs[n][0].render(GL_LINES)
996                                                 glColor3f(c/2, 0, c)
997                                                 self._gcodeVBOs[n][1].render(GL_LINES)
998                                                 glColor3f(0, c, c/2)
999                                                 self._gcodeVBOs[n][2].render(GL_LINES)
1000                                                 glColor3f(c, 0, 0)
1001                                                 self._gcodeVBOs[n][3].render(GL_LINES)
1002
1003                                                 glColor3f(0, c, 0)
1004                                                 self._gcodeVBOs[n][4].render(GL_LINES)
1005                                                 glColor3f(c/2, c/2, 0.0)
1006                                                 self._gcodeVBOs[n][5].render(GL_LINES)
1007                                                 glColor3f(0, c, c)
1008                                                 self._gcodeVBOs[n][6].render(GL_LINES)
1009                                                 self._gcodeVBOs[n][7].render(GL_LINES)
1010                                 glPopMatrix()
1011                 else:
1012                         glStencilFunc(GL_ALWAYS, 1, 1)
1013                         glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1014
1015                         if self.viewMode == 'overhang':
1016                                 self._objectOverhangShader.bind()
1017                                 self._objectOverhangShader.setUniform('cosAngle', math.cos(math.radians(90 - 60)))
1018                         else:
1019                                 self._objectShader.bind()
1020                         for obj in self._scene.objects():
1021                                 if obj._loadAnim is not None:
1022                                         if obj._loadAnim.isDone():
1023                                                 obj._loadAnim = None
1024                                         else:
1025                                                 continue
1026                                 brightness = 1.0
1027                                 if self._focusObj == obj:
1028                                         brightness = 1.2
1029                                 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
1030                                         brightness = 0.8
1031
1032                                 if self._selectedObj == obj or self._selectedObj is None:
1033                                         #If we want transparent, then first render a solid black model to remove the printer size lines.
1034                                         if self.viewMode == 'transparent':
1035                                                 glColor4f(0, 0, 0, 0)
1036                                                 self._renderObject(obj)
1037                                                 glEnable(GL_BLEND)
1038                                                 glBlendFunc(GL_ONE, GL_ONE)
1039                                                 glDisable(GL_DEPTH_TEST)
1040                                                 brightness *= 0.5
1041                                         if self.viewMode == 'xray':
1042                                                 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
1043                                         glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1044                                         glEnable(GL_STENCIL_TEST)
1045
1046                                 if self.viewMode == 'overhang':
1047                                         if self._selectedObj == obj and self.tempMatrix is not None:
1048                                                 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix() * self.tempMatrix)
1049                                         else:
1050                                                 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix())
1051
1052                                 if not self._scene.checkPlatform(obj):
1053                                         glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
1054                                         self._renderObject(obj)
1055                                 else:
1056                                         self._renderObject(obj, brightness)
1057                                 glDisable(GL_STENCIL_TEST)
1058                                 glDisable(GL_BLEND)
1059                                 glEnable(GL_DEPTH_TEST)
1060                                 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
1061
1062                         if self.viewMode == 'xray':
1063                                 glPushMatrix()
1064                                 glLoadIdentity()
1065                                 glEnable(GL_STENCIL_TEST)
1066                                 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
1067                                 glDisable(GL_DEPTH_TEST)
1068                                 for i in xrange(2, 15, 2):
1069                                         glStencilFunc(GL_EQUAL, i, 0xFF)
1070                                         glColor(float(i)/10, float(i)/10, float(i)/5)
1071                                         glBegin(GL_QUADS)
1072                                         glVertex3f(-1000,-1000,-10)
1073                                         glVertex3f( 1000,-1000,-10)
1074                                         glVertex3f( 1000, 1000,-10)
1075                                         glVertex3f(-1000, 1000,-10)
1076                                         glEnd()
1077                                 for i in xrange(1, 15, 2):
1078                                         glStencilFunc(GL_EQUAL, i, 0xFF)
1079                                         glColor(float(i)/10, 0, 0)
1080                                         glBegin(GL_QUADS)
1081                                         glVertex3f(-1000,-1000,-10)
1082                                         glVertex3f( 1000,-1000,-10)
1083                                         glVertex3f( 1000, 1000,-10)
1084                                         glVertex3f(-1000, 1000,-10)
1085                                         glEnd()
1086                                 glPopMatrix()
1087                                 glDisable(GL_STENCIL_TEST)
1088                                 glEnable(GL_DEPTH_TEST)
1089
1090                         self._objectShader.unbind()
1091
1092                         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1093                         glEnable(GL_BLEND)
1094                         if self._objectLoadShader is not None:
1095                                 self._objectLoadShader.bind()
1096                                 glColor4f(0.2, 0.6, 1.0, 1.0)
1097                                 for obj in self._scene.objects():
1098                                         if obj._loadAnim is None:
1099                                                 continue
1100                                         self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
1101                                         self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
1102                                         self._renderObject(obj)
1103                                 self._objectLoadShader.unbind()
1104                                 glDisable(GL_BLEND)
1105
1106                 self._drawMachine()
1107
1108                 if self._usbPrintMonitor.getState() == 'PRINTING' and self._usbPrintMonitor.getID() == self._slicer.getID():
1109                         glEnable(GL_BLEND)
1110                         z = self._usbPrintMonitor.getZ()
1111                         size = self._machineSize
1112                         glColor4ub(255,255,0,128)
1113                         glBegin(GL_QUADS)
1114                         glVertex3f(-size[0]/2,-size[1]/2, z)
1115                         glVertex3f( size[0]/2,-size[1]/2, z)
1116                         glVertex3f( size[0]/2, size[1]/2, z)
1117                         glVertex3f(-size[0]/2, size[1]/2, z)
1118                         glEnd()
1119
1120                 if self.viewMode == 'gcode':
1121                         if self._gcodeLoadThread is not None and self._gcodeLoadThread.isAlive():
1122                                 glDisable(GL_DEPTH_TEST)
1123                                 glPushMatrix()
1124                                 glLoadIdentity()
1125                                 glTranslate(0,-4,-10)
1126                                 glColor4ub(60,60,60,255)
1127                                 opengl.glDrawStringCenter(_("Loading toolpath for visualization..."))
1128                                 glPopMatrix()
1129                 else:
1130                         #Draw the object box-shadow, so you can see where it will collide with other objects.
1131                         if self._selectedObj is not None and len(self._scene.objects()) > 1:
1132                                 size = self._selectedObj.getSize()[0:2] / 2 + self._scene.getObjectExtend()
1133                                 glPushMatrix()
1134                                 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0)
1135                                 glEnable(GL_BLEND)
1136                                 glEnable(GL_CULL_FACE)
1137                                 glColor4f(0,0,0,0.12)
1138                                 glBegin(GL_QUADS)
1139                                 glVertex3f(-size[0],  size[1], 0.1)
1140                                 glVertex3f(-size[0], -size[1], 0.1)
1141                                 glVertex3f( size[0], -size[1], 0.1)
1142                                 glVertex3f( size[0],  size[1], 0.1)
1143                                 glEnd()
1144                                 glDisable(GL_CULL_FACE)
1145                                 glPopMatrix()
1146
1147                         #Draw the outline of the selected object, on top of everything else except the GUI.
1148                         if self._selectedObj is not None and self._selectedObj._loadAnim is None:
1149                                 glDisable(GL_DEPTH_TEST)
1150                                 glEnable(GL_CULL_FACE)
1151                                 glEnable(GL_STENCIL_TEST)
1152                                 glDisable(GL_BLEND)
1153                                 glStencilFunc(GL_EQUAL, 0, 255)
1154
1155                                 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
1156                                 glLineWidth(2)
1157                                 glColor4f(1,1,1,0.5)
1158                                 self._renderObject(self._selectedObj)
1159                                 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
1160
1161                                 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
1162                                 glDisable(GL_STENCIL_TEST)
1163                                 glDisable(GL_CULL_FACE)
1164                                 glEnable(GL_DEPTH_TEST)
1165
1166                         if self._selectedObj is not None:
1167                                 glPushMatrix()
1168                                 pos = self.getObjectCenterPos()
1169                                 glTranslate(pos[0], pos[1], pos[2])
1170                                 self.tool.OnDraw()
1171                                 glPopMatrix()
1172                 if self.viewMode == 'overhang' and not opengl.hasShaderSupport():
1173                         glDisable(GL_DEPTH_TEST)
1174                         glPushMatrix()
1175                         glLoadIdentity()
1176                         glTranslate(0,-4,-10)
1177                         glColor4ub(60,60,60,255)
1178                         opengl.glDrawStringCenter(_("Overhang view not working due to lack of OpenGL shaders support."))
1179                         glPopMatrix()
1180
1181         def _renderObject(self, obj, brightness = False, addSink = True):
1182                 glPushMatrix()
1183                 if addSink:
1184                         glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2 - profile.getProfileSettingFloat('object_sink'))
1185                 else:
1186                         glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
1187
1188                 if self.tempMatrix is not None and obj == self._selectedObj:
1189                         tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
1190                         glMultMatrixf(tempMatrix)
1191
1192                 offset = obj.getDrawOffset()
1193                 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
1194
1195                 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
1196                 glMultMatrixf(tempMatrix)
1197
1198                 n = 0
1199                 for m in obj._meshList:
1200                         if m.vbo is None:
1201                                 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
1202                         if brightness:
1203                                 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
1204                                 n += 1
1205                         m.vbo.render()
1206                 glPopMatrix()
1207
1208         def _drawMachine(self):
1209                 glEnable(GL_CULL_FACE)
1210                 glEnable(GL_BLEND)
1211
1212                 size = [profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')]
1213
1214                 machine = profile.getMachineSetting('machine_type')
1215                 if profile.getMachineSetting('machine_type').startswith('ultimaker'):
1216                         if machine not in self._platformMesh:
1217                                 meshes = meshLoader.loadMeshes(resources.getPathForMesh(machine + '_platform.stl'))
1218                                 if len(meshes) > 0:
1219                                         self._platformMesh[machine] = meshes[0]
1220                                 else:
1221                                         self._platformMesh[machine] = None
1222                                 if machine == 'ultimaker2':
1223                                         self._platformMesh[machine]._drawOffset = numpy.array([0,-37,145], numpy.float32)
1224                                 else:
1225                                         self._platformMesh[machine]._drawOffset = numpy.array([0,0,2.5], numpy.float32)
1226                         glColor4f(1,1,1,0.5)
1227                         self._objectShader.bind()
1228                         self._renderObject(self._platformMesh[machine], False, False)
1229                         self._objectShader.unbind()
1230
1231                         #For the Ultimaker 2 render the texture on the back plate to show the Ultimaker2 text.
1232                         if machine == 'ultimaker2':
1233                                 if not hasattr(self._platformMesh[machine], 'texture'):
1234                                         self._platformMesh[machine].texture = opengl.loadGLTexture('Ultimaker2backplate.png')
1235                                 glBindTexture(GL_TEXTURE_2D, self._platformMesh[machine].texture)
1236                                 glEnable(GL_TEXTURE_2D)
1237                                 glPushMatrix()
1238                                 glColor4f(1,1,1,1)
1239
1240                                 glTranslate(0,150,-5)
1241                                 h = 50
1242                                 d = 8
1243                                 w = 100
1244                                 glEnable(GL_BLEND)
1245                                 glBlendFunc(GL_DST_COLOR, GL_ZERO)
1246                                 glBegin(GL_QUADS)
1247                                 glTexCoord2f(1, 0)
1248                                 glVertex3f( w, 0, h)
1249                                 glTexCoord2f(0, 0)
1250                                 glVertex3f(-w, 0, h)
1251                                 glTexCoord2f(0, 1)
1252                                 glVertex3f(-w, 0, 0)
1253                                 glTexCoord2f(1, 1)
1254                                 glVertex3f( w, 0, 0)
1255
1256                                 glTexCoord2f(1, 0)
1257                                 glVertex3f(-w, d, h)
1258                                 glTexCoord2f(0, 0)
1259                                 glVertex3f( w, d, h)
1260                                 glTexCoord2f(0, 1)
1261                                 glVertex3f( w, d, 0)
1262                                 glTexCoord2f(1, 1)
1263                                 glVertex3f(-w, d, 0)
1264                                 glEnd()
1265                                 glDisable(GL_TEXTURE_2D)
1266                                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1267                                 glPopMatrix()
1268                 else:
1269                         glColor4f(0,0,0,1)
1270                         glLineWidth(3)
1271                         glBegin(GL_LINES)
1272                         glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1273                         glVertex3f(-size[0] / 2, -size[1] / 2, 10)
1274                         glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1275                         glVertex3f(-size[0] / 2+10, -size[1] / 2, 0)
1276                         glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1277                         glVertex3f(-size[0] / 2, -size[1] / 2+10, 0)
1278                         glEnd()
1279
1280                 v0 = [ size[0] / 2, size[1] / 2, size[2]]
1281                 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
1282                 v2 = [-size[0] / 2, size[1] / 2, size[2]]
1283                 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
1284                 v4 = [ size[0] / 2, size[1] / 2, 0]
1285                 v5 = [ size[0] / 2,-size[1] / 2, 0]
1286                 v6 = [-size[0] / 2, size[1] / 2, 0]
1287                 v7 = [-size[0] / 2,-size[1] / 2, 0]
1288
1289                 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
1290                 glEnableClientState(GL_VERTEX_ARRAY)
1291                 glVertexPointer(3, GL_FLOAT, 3*4, vList)
1292
1293                 glColor4ub(5, 171, 231, 64)
1294                 glDrawArrays(GL_QUADS, 0, 4)
1295                 glColor4ub(5, 171, 231, 96)
1296                 glDrawArrays(GL_QUADS, 4, 8)
1297                 glColor4ub(5, 171, 231, 128)
1298                 glDrawArrays(GL_QUADS, 12, 8)
1299                 glDisableClientState(GL_VERTEX_ARRAY)
1300
1301                 sx = self._machineSize[0]
1302                 sy = self._machineSize[1]
1303                 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
1304                         for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
1305                                 x1 = x * 10
1306                                 x2 = x1 + 10
1307                                 y1 = y * 10
1308                                 y2 = y1 + 10
1309                                 x1 = max(min(x1, sx/2), -sx/2)
1310                                 y1 = max(min(y1, sy/2), -sy/2)
1311                                 x2 = max(min(x2, sx/2), -sx/2)
1312                                 y2 = max(min(y2, sy/2), -sy/2)
1313                                 if (x & 1) == (y & 1):
1314                                         glColor4ub(5, 171, 231, 127)
1315                                 else:
1316                                         glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
1317                                 glBegin(GL_QUADS)
1318                                 glVertex3f(x1, y1, -0.02)
1319                                 glVertex3f(x2, y1, -0.02)
1320                                 glVertex3f(x2, y2, -0.02)
1321                                 glVertex3f(x1, y2, -0.02)
1322                                 glEnd()
1323
1324                 glDisable(GL_BLEND)
1325                 glDisable(GL_CULL_FACE)
1326
1327         def _generateGCodeVBOs(self, layer):
1328                 ret = []
1329                 for extrudeType in ['WALL-OUTER:0', 'WALL-OUTER:1', 'WALL-OUTER:2', 'WALL-OUTER:3', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1330                         if ':' in extrudeType:
1331                                 extruder = int(extrudeType[extrudeType.find(':')+1:])
1332                                 extrudeType = extrudeType[0:extrudeType.find(':')]
1333                         else:
1334                                 extruder = None
1335                         pointList = numpy.zeros((0,3), numpy.float32)
1336                         for path in layer:
1337                                 if path['type'] == 'extrude' and path['pathType'] == extrudeType and (extruder is None or path['extruder'] == extruder):
1338                                         a = path['points']
1339                                         a = numpy.concatenate((a[:-1], a[1:]), 1)
1340                                         a = a.reshape((len(a) * 2, 3))
1341                                         pointList = numpy.concatenate((pointList, a))
1342                         ret.append(opengl.GLVBO(pointList))
1343                 return ret
1344
1345         def _generateGCodeVBOs2(self, layer):
1346                 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
1347                 filamentArea = math.pi * filamentRadius * filamentRadius
1348                 useFilamentArea = profile.getMachineSetting('gcode_flavor') == 'UltiGCode'
1349
1350                 ret = []
1351                 for extrudeType in ['WALL-OUTER:0', 'WALL-OUTER:1', 'WALL-OUTER:2', 'WALL-OUTER:3', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1352                         if ':' in extrudeType:
1353                                 extruder = int(extrudeType[extrudeType.find(':')+1:])
1354                                 extrudeType = extrudeType[0:extrudeType.find(':')]
1355                         else:
1356                                 extruder = None
1357                         pointList = numpy.zeros((0,3), numpy.float32)
1358                         for path in layer:
1359                                 if path['type'] == 'extrude' and path['pathType'] == extrudeType and (extruder is None or path['extruder'] == extruder):
1360                                         a = path['points']
1361                                         if extrudeType == 'FILL':
1362                                                 a[:,2] += 0.01
1363
1364                                         normal = a[1:] - a[:-1]
1365                                         lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
1366                                         normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
1367                                         normal[:,2] /= lens
1368
1369                                         ePerDist = path['extrusion'][1:] / lens
1370                                         if useFilamentArea:
1371                                                 lineWidth = ePerDist / path['layerThickness'] / 2.0
1372                                         else:
1373                                                 lineWidth = ePerDist * (filamentArea / path['layerThickness'] / 2)
1374
1375                                         normal[:,0] *= lineWidth
1376                                         normal[:,1] *= lineWidth
1377
1378                                         b = numpy.zeros((len(a)-1, 0), numpy.float32)
1379                                         b = numpy.concatenate((b, a[1:] + normal), 1)
1380                                         b = numpy.concatenate((b, a[1:] - normal), 1)
1381                                         b = numpy.concatenate((b, a[:-1] - normal), 1)
1382                                         b = numpy.concatenate((b, a[:-1] + normal), 1)
1383                                         b = b.reshape((len(b) * 4, 3))
1384
1385                                         if len(a) > 2:
1386                                                 normal2 = normal[:-1] + normal[1:]
1387                                                 lens2 = numpy.sqrt(normal2[:,0]**2 + normal2[:,1]**2)
1388                                                 normal2[:,0] /= lens2
1389                                                 normal2[:,1] /= lens2
1390                                                 normal2[:,0] *= lineWidth[:-1]
1391                                                 normal2[:,1] *= lineWidth[:-1]
1392
1393                                                 c = numpy.zeros((len(a)-2, 0), numpy.float32)
1394                                                 c = numpy.concatenate((c, a[1:-1]), 1)
1395                                                 c = numpy.concatenate((c, a[1:-1]+normal[1:]), 1)
1396                                                 c = numpy.concatenate((c, a[1:-1]+normal2), 1)
1397                                                 c = numpy.concatenate((c, a[1:-1]+normal[:-1]), 1)
1398
1399                                                 c = numpy.concatenate((c, a[1:-1]), 1)
1400                                                 c = numpy.concatenate((c, a[1:-1]-normal[1:]), 1)
1401                                                 c = numpy.concatenate((c, a[1:-1]-normal2), 1)
1402                                                 c = numpy.concatenate((c, a[1:-1]-normal[:-1]), 1)
1403
1404                                                 c = c.reshape((len(c) * 8, 3))
1405
1406                                                 pointList = numpy.concatenate((pointList, b, c))
1407                                         else:
1408                                                 pointList = numpy.concatenate((pointList, b))
1409                         ret.append(opengl.GLVBO(pointList))
1410
1411                 pointList = numpy.zeros((0,3), numpy.float32)
1412                 for path in layer:
1413                         if path['type'] == 'move':
1414                                 a = path['points'] + numpy.array([0,0,0.01], numpy.float32)
1415                                 a = numpy.concatenate((a[:-1], a[1:]), 1)
1416                                 a = a.reshape((len(a) * 2, 3))
1417                                 pointList = numpy.concatenate((pointList, a))
1418                         if path['type'] == 'retract':
1419                                 a = path['points'] + numpy.array([0,0,0.01], numpy.float32)
1420                                 a = numpy.concatenate((a[:-1], a[1:] + numpy.array([0,0,1], numpy.float32)), 1)
1421                                 a = a.reshape((len(a) * 2, 3))
1422                                 pointList = numpy.concatenate((pointList, a))
1423                 ret.append(opengl.GLVBO(pointList))
1424
1425                 return ret
1426
1427         def getObjectCenterPos(self):
1428                 if self._selectedObj is None:
1429                         return [0.0, 0.0, 0.0]
1430                 pos = self._selectedObj.getPosition()
1431                 size = self._selectedObj.getSize()
1432                 return [pos[0], pos[1], size[2]/2 - profile.getProfileSettingFloat('object_sink')]
1433
1434         def getObjectBoundaryCircle(self):
1435                 if self._selectedObj is None:
1436                         return 0.0
1437                 return self._selectedObj.getBoundaryCircle()
1438
1439         def getObjectSize(self):
1440                 if self._selectedObj is None:
1441                         return [0.0, 0.0, 0.0]
1442                 return self._selectedObj.getSize()
1443
1444         def getObjectMatrix(self):
1445                 if self._selectedObj is None:
1446                         return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1447                 return self._selectedObj.getMatrix()
1448
1449 class shaderEditor(wx.Dialog):
1450         def __init__(self, parent, callback, v, f):
1451                 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1452                 self._callback = callback
1453                 s = wx.BoxSizer(wx.VERTICAL)
1454                 self.SetSizer(s)
1455                 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1456                 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1457                 s.Add(self._vertex, 1, flag=wx.EXPAND)
1458                 s.Add(self._fragment, 1, flag=wx.EXPAND)
1459
1460                 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1461                 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1462
1463                 self.SetPosition(self.GetParent().GetPosition())
1464                 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1465                 self.Show()
1466
1467         def OnText(self, e):
1468                 self._callback(self._vertex.GetValue(), self._fragment.GetValue())