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