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