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