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._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                 if self.viewMode == 'overhang' and not opengl.hasShaderSupport():
1067                         glDisable(GL_DEPTH_TEST)
1068                         glPushMatrix()
1069                         glLoadIdentity()
1070                         glTranslate(0,-4,-10)
1071                         glColor4ub(60,60,60,255)
1072                         opengl.glDrawStringCenter('Overhang view not working due to lack of OpenGL shaders support.')
1073                         glPopMatrix()
1074
1075         def _renderObject(self, obj, brightness = False, addSink = True):
1076                 glPushMatrix()
1077                 if addSink:
1078                         glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2 - profile.getProfileSettingFloat('object_sink'))
1079                 else:
1080                         glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
1081
1082                 if self.tempMatrix is not None and obj == self._selectedObj:
1083                         tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
1084                         glMultMatrixf(tempMatrix)
1085
1086                 offset = obj.getDrawOffset()
1087                 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
1088
1089                 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
1090                 glMultMatrixf(tempMatrix)
1091
1092                 n = 0
1093                 for m in obj._meshList:
1094                         if m.vbo is None:
1095                                 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
1096                         if brightness:
1097                                 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
1098                                 n += 1
1099                         m.vbo.render()
1100                 glPopMatrix()
1101
1102         def _drawMachine(self):
1103                 glEnable(GL_CULL_FACE)
1104                 glEnable(GL_BLEND)
1105
1106                 if profile.getPreference('machine_type') == 'ultimaker':
1107                         glColor4f(1,1,1,0.5)
1108                         self._objectShader.bind()
1109                         self._renderObject(self._platformMesh, False, False)
1110                         self._objectShader.unbind()
1111
1112                 size = [profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')]
1113                 v0 = [ size[0] / 2, size[1] / 2, size[2]]
1114                 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
1115                 v2 = [-size[0] / 2, size[1] / 2, size[2]]
1116                 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
1117                 v4 = [ size[0] / 2, size[1] / 2, 0]
1118                 v5 = [ size[0] / 2,-size[1] / 2, 0]
1119                 v6 = [-size[0] / 2, size[1] / 2, 0]
1120                 v7 = [-size[0] / 2,-size[1] / 2, 0]
1121
1122                 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
1123                 glEnableClientState(GL_VERTEX_ARRAY)
1124                 glVertexPointer(3, GL_FLOAT, 3*4, vList)
1125
1126                 glColor4ub(5, 171, 231, 64)
1127                 glDrawArrays(GL_QUADS, 0, 4)
1128                 glColor4ub(5, 171, 231, 96)
1129                 glDrawArrays(GL_QUADS, 4, 8)
1130                 glColor4ub(5, 171, 231, 128)
1131                 glDrawArrays(GL_QUADS, 12, 8)
1132
1133                 sx = self._machineSize[0]
1134                 sy = self._machineSize[1]
1135                 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
1136                         for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
1137                                 x1 = x * 10
1138                                 x2 = x1 + 10
1139                                 y1 = y * 10
1140                                 y2 = y1 + 10
1141                                 x1 = max(min(x1, sx/2), -sx/2)
1142                                 y1 = max(min(y1, sy/2), -sy/2)
1143                                 x2 = max(min(x2, sx/2), -sx/2)
1144                                 y2 = max(min(y2, sy/2), -sy/2)
1145                                 if (x & 1) == (y & 1):
1146                                         glColor4ub(5, 171, 231, 127)
1147                                 else:
1148                                         glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
1149                                 glBegin(GL_QUADS)
1150                                 glVertex3f(x1, y1, -0.02)
1151                                 glVertex3f(x2, y1, -0.02)
1152                                 glVertex3f(x2, y2, -0.02)
1153                                 glVertex3f(x1, y2, -0.02)
1154                                 glEnd()
1155
1156                 glDisableClientState(GL_VERTEX_ARRAY)
1157                 glDisable(GL_BLEND)
1158                 glDisable(GL_CULL_FACE)
1159
1160         def _generateGCodeVBOs(self, layer):
1161                 ret = []
1162                 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1163                         pointList = numpy.zeros((0,3), numpy.float32)
1164                         for path in layer:
1165                                 if path['type'] == 'extrude' and path['pathType'] == extrudeType:
1166                                         a = path['points']
1167                                         a = numpy.concatenate((a[:-1], a[1:]), 1)
1168                                         a = a.reshape((len(a) * 2, 3))
1169                                         pointList = numpy.concatenate((pointList, a))
1170                         ret.append(opengl.GLVBO(pointList))
1171                 return ret
1172
1173         def _generateGCodeVBOs2(self, layer):
1174                 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
1175                 filamentArea = math.pi * filamentRadius * filamentRadius
1176
1177                 ret = []
1178                 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1179                         pointList = numpy.zeros((0,3), numpy.float32)
1180                         for path in layer:
1181                                 if path['type'] == 'extrude' and path['pathType'] == extrudeType:
1182                                         a = path['points']
1183                                         if extrudeType == 'FILL':
1184                                                 a[:,2] += 0.01
1185
1186                                         normal = a[1:] - a[:-1]
1187                                         lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
1188                                         normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
1189                                         normal[:,2] /= lens
1190
1191                                         ePerDist = path['extrusion'][1:] / lens
1192                                         lineWidth = ePerDist * (filamentArea / path['layerThickness'] / 2)
1193
1194                                         normal[:,0] *= lineWidth
1195                                         normal[:,1] *= lineWidth
1196
1197                                         b = numpy.zeros((len(a)-1, 0), numpy.float32)
1198                                         b = numpy.concatenate((b, a[1:] + normal), 1)
1199                                         b = numpy.concatenate((b, a[1:] - normal), 1)
1200                                         b = numpy.concatenate((b, a[:-1] - normal), 1)
1201                                         b = numpy.concatenate((b, a[:-1] + normal), 1)
1202                                         #b = numpy.concatenate((b, a[:-1]), 1)
1203                                         #b = numpy.concatenate((b, a[:-1]), 1)
1204                                         b = b.reshape((len(b) * 4, 3))
1205
1206                                         if len(a) > 2:
1207                                                 normal2 = normal[:-1] + normal[1:]
1208                                                 lens2 = numpy.sqrt(normal2[:,0]**2 + normal2[:,1]**2)
1209                                                 normal2[:,0] /= lens2
1210                                                 normal2[:,1] /= lens2
1211                                                 normal2[:,0] *= lineWidth[:-1]
1212                                                 normal2[:,1] *= lineWidth[:-1]
1213
1214                                                 c = numpy.zeros((len(a)-2, 0), numpy.float32)
1215                                                 c = numpy.concatenate((c, a[1:-1]), 1)
1216                                                 c = numpy.concatenate((c, a[1:-1]+normal[1:]), 1)
1217                                                 c = numpy.concatenate((c, a[1:-1]+normal2), 1)
1218                                                 c = numpy.concatenate((c, a[1:-1]+normal[:-1]), 1)
1219
1220                                                 c = numpy.concatenate((c, a[1:-1]), 1)
1221                                                 c = numpy.concatenate((c, a[1:-1]-normal[1:]), 1)
1222                                                 c = numpy.concatenate((c, a[1:-1]-normal2), 1)
1223                                                 c = numpy.concatenate((c, a[1:-1]-normal[:-1]), 1)
1224
1225                                                 c = c.reshape((len(c) * 8, 3))
1226
1227                                                 pointList = numpy.concatenate((pointList, b, c))
1228                                         else:
1229                                                 pointList = numpy.concatenate((pointList, b))
1230                         ret.append(opengl.GLVBO(pointList))
1231
1232                 pointList = numpy.zeros((0,3), numpy.float32)
1233                 for path in layer:
1234                         if path['type'] == 'move':
1235                                 a = path['points'] + numpy.array([0,0,0.01], numpy.float32)
1236                                 a = numpy.concatenate((a[:-1], a[1:]), 1)
1237                                 a = a.reshape((len(a) * 2, 3))
1238                                 pointList = numpy.concatenate((pointList, a))
1239                         if path['type'] == 'retract':
1240                                 a = path['points'] + numpy.array([0,0,0.01], numpy.float32)
1241                                 a = numpy.concatenate((a[:-1], a[1:] + numpy.array([0,0,1], numpy.float32)), 1)
1242                                 a = a.reshape((len(a) * 2, 3))
1243                                 pointList = numpy.concatenate((pointList, a))
1244                 ret.append(opengl.GLVBO(pointList))
1245
1246                 return ret
1247
1248         def getObjectCenterPos(self):
1249                 if self._selectedObj is None:
1250                         return [0.0, 0.0, 0.0]
1251                 pos = self._selectedObj.getPosition()
1252                 size = self._selectedObj.getSize()
1253                 return [pos[0], pos[1], size[2]/2 - profile.getProfileSettingFloat('object_sink')]
1254
1255         def getObjectBoundaryCircle(self):
1256                 if self._selectedObj is None:
1257                         return 0.0
1258                 return self._selectedObj.getBoundaryCircle()
1259
1260         def getObjectSize(self):
1261                 if self._selectedObj is None:
1262                         return [0.0, 0.0, 0.0]
1263                 return self._selectedObj.getSize()
1264
1265         def getObjectMatrix(self):
1266                 if self._selectedObj is None:
1267                         return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1268                 return self._selectedObj.getMatrix()
1269
1270 class shaderEditor(wx.Dialog):
1271         def __init__(self, parent, callback, v, f):
1272                 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1273                 self._callback = callback
1274                 s = wx.BoxSizer(wx.VERTICAL)
1275                 self.SetSizer(s)
1276                 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1277                 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1278                 s.Add(self._vertex, 1, flag=wx.EXPAND)
1279                 s.Add(self._fragment, 1, flag=wx.EXPAND)
1280
1281                 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1282                 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1283
1284                 self.SetPosition(self.GetParent().GetPosition())
1285                 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1286                 self.Show()
1287
1288         def OnText(self, e):
1289                 self._callback(self._vertex.GetValue(), self._fragment.GetValue())