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