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