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