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