chiark / gitweb /
Keep focus on objects even if the mouse is out of the window, else the right mouse...
[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
693         def getMouseRay(self, x, y):
694                 if self._viewport is None:
695                         return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
696                 p0 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
697                 p1 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
698                 p0 -= self._viewTarget
699                 p1 -= self._viewTarget
700                 return p0, p1
701
702         def _init3DView(self):
703                 # set viewing projection
704                 size = self.GetSize()
705                 glViewport(0, 0, size.GetWidth(), size.GetHeight())
706                 glLoadIdentity()
707
708                 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
709
710                 glDisable(GL_RESCALE_NORMAL)
711                 glDisable(GL_LIGHTING)
712                 glDisable(GL_LIGHT0)
713                 glEnable(GL_DEPTH_TEST)
714                 glDisable(GL_CULL_FACE)
715                 glDisable(GL_BLEND)
716                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
717
718                 glClearColor(0.8, 0.8, 0.8, 1.0)
719                 glClearStencil(0)
720                 glClearDepth(1.0)
721
722                 glMatrixMode(GL_PROJECTION)
723                 glLoadIdentity()
724                 aspect = float(size.GetWidth()) / float(size.GetHeight())
725                 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
726
727                 glMatrixMode(GL_MODELVIEW)
728                 glLoadIdentity()
729                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
730
731         def OnPaint(self,e):
732                 if machineCom.machineIsConnected():
733                         self.printButton._imageID = 6
734                         self.printButton._tooltip = 'Print'
735                 elif len(removableStorage.getPossibleSDcardDrives()) > 0:
736                         self.printButton._imageID = 2
737                         self.printButton._tooltip = 'Toolpath to SD'
738                 else:
739                         self.printButton._imageID = 3
740                         self.printButton._tooltip = 'Save toolpath'
741
742                 if self._animView is not None:
743                         self._viewTarget = self._animView.getPosition()
744                         if self._animView.isDone():
745                                 self._animView = None
746                 if self._animZoom is not None:
747                         self._zoom = self._animZoom.getPosition()
748                         if self._animZoom.isDone():
749                                 self._animZoom = None
750                 if self.viewMode == 'gcode' and self._gcode is not None:
751                         try:
752                                 self._viewTarget[2] = self._gcode.layerList[self.layerSelect.getValue()][-1]['points'][0][2]
753                         except:
754                                 pass
755                 if self._objectShader is None:
756                         if opengl.hasShaderSupport():
757                                 self._objectShader = opengl.GLShader("""
758 varying float light_amount;
759
760 void main(void)
761 {
762     gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
763     gl_FrontColor = gl_Color;
764
765         light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
766         light_amount += 0.2;
767 }
768                                 ""","""
769 varying float light_amount;
770
771 void main(void)
772 {
773         gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
774 }
775                                 """)
776                                 self._objectOverhangShader = opengl.GLShader("""
777 uniform float cosAngle;
778 uniform mat3 rotMatrix;
779 varying float light_amount;
780
781 void main(void)
782 {
783     gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
784     gl_FrontColor = gl_Color;
785
786         light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
787         light_amount += 0.2;
788         if (normalize(rotMatrix * gl_Normal).z < -cosAngle)
789         {
790                 light_amount = -10.0;
791         }
792 }
793                                 ""","""
794 varying float light_amount;
795
796 void main(void)
797 {
798         if (light_amount == -10.0)
799         {
800                 gl_FragColor = vec4(1.0, 0.0, 0.0, gl_Color[3]);
801         }else{
802                 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
803         }
804 }
805                                 """)
806                                 self._objectLoadShader = opengl.GLShader("""
807 uniform float intensity;
808 uniform float scale;
809 varying float light_amount;
810
811 void main(void)
812 {
813         vec4 tmp = gl_Vertex;
814     tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
815     tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
816     gl_Position = gl_ModelViewProjectionMatrix * tmp;
817     gl_FrontColor = gl_Color;
818
819         light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
820         light_amount += 0.2;
821 }
822                         ""","""
823 uniform float intensity;
824 varying float light_amount;
825
826 void main(void)
827 {
828         gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
829 }
830                                 """)
831                         else:
832                                 self._objectShader = opengl.GLFakeShader()
833                                 self._objectOverhangShader = opengl.GLFakeShader()
834                                 self._objectLoadShader = None
835                 self._init3DView()
836                 glTranslate(0,0,-self._zoom)
837                 glRotate(-self._pitch, 1,0,0)
838                 glRotate(self._yaw, 0,0,1)
839                 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
840
841                 self._viewport = glGetIntegerv(GL_VIEWPORT)
842                 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
843                 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
844
845                 glClearColor(1,1,1,1)
846                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
847
848                 if self.viewMode != 'gcode':
849                         for n in xrange(0, len(self._scene.objects())):
850                                 obj = self._scene.objects()[n]
851                                 glColor4ub((n >> 16) & 0xFF, (n >> 8) & 0xFF, (n >> 0) & 0xFF, 0xFF)
852                                 self._renderObject(obj)
853
854                 if self._mouseX > -1:
855                         glFlush()
856                         n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0] >> 8
857                         if n < len(self._scene.objects()):
858                                 self._focusObj = self._scene.objects()[n]
859                         else:
860                                 self._focusObj = None
861                         f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
862                         #self.GetTopLevelParent().SetTitle(hex(n) + " " + str(f))
863                         self._mouse3Dpos = opengl.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
864                         self._mouse3Dpos -= self._viewTarget
865
866                 self._init3DView()
867                 glTranslate(0,0,-self._zoom)
868                 glRotate(-self._pitch, 1,0,0)
869                 glRotate(self._yaw, 0,0,1)
870                 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
871
872                 if self.viewMode == 'gcode':
873                         if self._gcode is not None and self._gcode.layerList is None:
874                                 self._gcodeLoadThread = threading.Thread(target=self._loadGCode)
875                                 self._gcodeLoadThread.daemon = True
876                                 self._gcodeLoadThread.start()
877                         if self._gcode is not None and self._gcode.layerList is not None:
878                                 glPushMatrix()
879                                 glTranslate(-self._machineSize[0] / 2, -self._machineSize[1] / 2, 0)
880                                 t = time.time()
881                                 drawUpTill = min(len(self._gcode.layerList), self.layerSelect.getValue() + 1)
882                                 for n in xrange(0, drawUpTill):
883                                         c = 1.0 - float(drawUpTill - n) / 15
884                                         c = max(0.3, c)
885                                         if len(self._gcodeVBOs) < n + 1:
886                                                 self._gcodeVBOs.append(self._generateGCodeVBOs(self._gcode.layerList[n]))
887                                                 if time.time() - t > 0.5:
888                                                         self.QueueRefresh()
889                                                         break
890                                         #['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']
891                                         if n == drawUpTill - 1:
892                                                 if len(self._gcodeVBOs[n]) < 6:
893                                                         self._gcodeVBOs[n] += self._generateGCodeVBOs2(self._gcode.layerList[n])
894                                                 glColor3f(c, 0, 0)
895                                                 self._gcodeVBOs[n][5].render(GL_QUADS)
896                                                 glColor3f(0, c, 0)
897                                                 self._gcodeVBOs[n][6].render(GL_QUADS)
898                                                 glColor3f(c/2, c/2, 0.0)
899                                                 self._gcodeVBOs[n][7].render(GL_QUADS)
900                                                 glColor3f(0, c, c)
901                                                 self._gcodeVBOs[n][8].render(GL_QUADS)
902                                                 self._gcodeVBOs[n][9].render(GL_QUADS)
903                                                 glColor3f(0, 0, c)
904                                                 self._gcodeVBOs[n][10].render(GL_LINES)
905                                         else:
906                                                 glColor3f(c, 0, 0)
907                                                 self._gcodeVBOs[n][0].render(GL_LINES)
908                                                 glColor3f(0, c, 0)
909                                                 self._gcodeVBOs[n][1].render(GL_LINES)
910                                                 glColor3f(c/2, c/2, 0.0)
911                                                 self._gcodeVBOs[n][2].render(GL_LINES)
912                                                 glColor3f(0, c, c)
913                                                 self._gcodeVBOs[n][3].render(GL_LINES)
914                                                 self._gcodeVBOs[n][4].render(GL_LINES)
915                                 glPopMatrix()
916                 else:
917                         glStencilFunc(GL_ALWAYS, 1, 1)
918                         glStencilOp(GL_INCR, GL_INCR, GL_INCR)
919
920                         if self.viewMode == 'overhang':
921                                 self._objectOverhangShader.bind()
922                                 self._objectOverhangShader.setUniform('cosAngle', math.cos(math.radians(90 - 60)))
923                         else:
924                                 self._objectShader.bind()
925                         for obj in self._scene.objects():
926                                 if obj._loadAnim is not None:
927                                         if obj._loadAnim.isDone():
928                                                 obj._loadAnim = None
929                                         else:
930                                                 continue
931                                 brightness = 1.0
932                                 if self._focusObj == obj:
933                                         brightness = 1.2
934                                 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
935                                         brightness = 0.8
936
937                                 if self._selectedObj == obj or self._selectedObj is None:
938                                         #If we want transparent, then first render a solid black model to remove the printer size lines.
939                                         if self.viewMode == 'transparent':
940                                                 glColor4f(0, 0, 0, 0)
941                                                 self._renderObject(obj)
942                                                 glEnable(GL_BLEND)
943                                                 glBlendFunc(GL_ONE, GL_ONE)
944                                                 glDisable(GL_DEPTH_TEST)
945                                                 brightness *= 0.5
946                                         if self.viewMode == 'xray':
947                                                 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
948                                         glStencilOp(GL_INCR, GL_INCR, GL_INCR)
949                                         glEnable(GL_STENCIL_TEST)
950
951                                 if self.viewMode == 'overhang':
952                                         if self._selectedObj == obj and self.tempMatrix is not None:
953                                                 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix() * self.tempMatrix)
954                                         else:
955                                                 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix())
956
957                                 if not self._scene.checkPlatform(obj):
958                                         glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
959                                         self._renderObject(obj)
960                                 else:
961                                         self._renderObject(obj, brightness)
962                                 glDisable(GL_STENCIL_TEST)
963                                 glDisable(GL_BLEND)
964                                 glEnable(GL_DEPTH_TEST)
965                                 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
966
967                         if self.viewMode == 'xray':
968                                 glPushMatrix()
969                                 glLoadIdentity()
970                                 glEnable(GL_STENCIL_TEST)
971                                 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
972                                 glDisable(GL_DEPTH_TEST)
973                                 for i in xrange(2, 15, 2):
974                                         glStencilFunc(GL_EQUAL, i, 0xFF)
975                                         glColor(float(i)/10, float(i)/10, float(i)/5)
976                                         glBegin(GL_QUADS)
977                                         glVertex3f(-1000,-1000,-10)
978                                         glVertex3f( 1000,-1000,-10)
979                                         glVertex3f( 1000, 1000,-10)
980                                         glVertex3f(-1000, 1000,-10)
981                                         glEnd()
982                                 for i in xrange(1, 15, 2):
983                                         glStencilFunc(GL_EQUAL, i, 0xFF)
984                                         glColor(float(i)/10, 0, 0)
985                                         glBegin(GL_QUADS)
986                                         glVertex3f(-1000,-1000,-10)
987                                         glVertex3f( 1000,-1000,-10)
988                                         glVertex3f( 1000, 1000,-10)
989                                         glVertex3f(-1000, 1000,-10)
990                                         glEnd()
991                                 glPopMatrix()
992                                 glDisable(GL_STENCIL_TEST)
993                                 glEnable(GL_DEPTH_TEST)
994
995                         self._objectShader.unbind()
996
997                         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
998                         glEnable(GL_BLEND)
999                         if self._objectLoadShader is not None:
1000                                 self._objectLoadShader.bind()
1001                                 glColor4f(0.2, 0.6, 1.0, 1.0)
1002                                 for obj in self._scene.objects():
1003                                         if obj._loadAnim is None:
1004                                                 continue
1005                                         self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
1006                                         self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
1007                                         self._renderObject(obj)
1008                                 self._objectLoadShader.unbind()
1009                                 glDisable(GL_BLEND)
1010
1011                 self._drawMachine()
1012
1013                 if self.viewMode == 'gcode':
1014                         if self._gcodeLoadThread is not None and self._gcodeLoadThread.isAlive():
1015                                 glDisable(GL_DEPTH_TEST)
1016                                 glPushMatrix()
1017                                 glLoadIdentity()
1018                                 glTranslate(0,-4,-10)
1019                                 glColor4ub(60,60,60,255)
1020                                 opengl.glDrawStringCenter('Loading toolpath for visualization...')
1021                                 glPopMatrix()
1022                 else:
1023                         #Draw the object box-shadow, so you can see where it will collide with other objects.
1024                         if self._selectedObj is not None and len(self._scene.objects()) > 1:
1025                                 size = self._selectedObj.getSize()[0:2] / 2 + self._scene.getObjectExtend()
1026                                 glPushMatrix()
1027                                 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0)
1028                                 glEnable(GL_BLEND)
1029                                 glEnable(GL_CULL_FACE)
1030                                 glColor4f(0,0,0,0.12)
1031                                 glBegin(GL_QUADS)
1032                                 glVertex3f(-size[0],  size[1], 0.1)
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                                 glEnd()
1037                                 glDisable(GL_CULL_FACE)
1038                                 glPopMatrix()
1039
1040                         #Draw the outline of the selected object, on top of everything else except the GUI.
1041                         if self._selectedObj is not None and self._selectedObj._loadAnim is None:
1042                                 glDisable(GL_DEPTH_TEST)
1043                                 glEnable(GL_CULL_FACE)
1044                                 glEnable(GL_STENCIL_TEST)
1045                                 glDisable(GL_BLEND)
1046                                 glStencilFunc(GL_EQUAL, 0, 255)
1047
1048                                 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
1049                                 glLineWidth(2)
1050                                 glColor4f(1,1,1,0.5)
1051                                 self._renderObject(self._selectedObj)
1052                                 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
1053
1054                                 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
1055                                 glDisable(GL_STENCIL_TEST)
1056                                 glDisable(GL_CULL_FACE)
1057                                 glEnable(GL_DEPTH_TEST)
1058
1059                         if self._selectedObj is not None:
1060                                 glPushMatrix()
1061                                 pos = self.getObjectCenterPos()
1062                                 glTranslate(pos[0], pos[1], pos[2])
1063                                 self.tool.OnDraw()
1064                                 glPopMatrix()
1065                 if self.viewMode == 'overhang' and not opengl.hasShaderSupport():
1066                         glDisable(GL_DEPTH_TEST)
1067                         glPushMatrix()
1068                         glLoadIdentity()
1069                         glTranslate(0,-4,-10)
1070                         glColor4ub(60,60,60,255)
1071                         opengl.glDrawStringCenter('Overhang view not working due to lack of OpenGL shaders support.')
1072                         glPopMatrix()
1073
1074         def _renderObject(self, obj, brightness = False, addSink = True):
1075                 glPushMatrix()
1076                 if addSink:
1077                         glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2 - profile.getProfileSettingFloat('object_sink'))
1078                 else:
1079                         glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
1080
1081                 if self.tempMatrix is not None and obj == self._selectedObj:
1082                         tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
1083                         glMultMatrixf(tempMatrix)
1084
1085                 offset = obj.getDrawOffset()
1086                 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
1087
1088                 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
1089                 glMultMatrixf(tempMatrix)
1090
1091                 n = 0
1092                 for m in obj._meshList:
1093                         if m.vbo is None:
1094                                 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
1095                         if brightness:
1096                                 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
1097                                 n += 1
1098                         m.vbo.render()
1099                 glPopMatrix()
1100
1101         def _drawMachine(self):
1102                 glEnable(GL_CULL_FACE)
1103                 glEnable(GL_BLEND)
1104
1105                 if profile.getPreference('machine_type') == 'ultimaker':
1106                         glColor4f(1,1,1,0.5)
1107                         self._objectShader.bind()
1108                         self._renderObject(self._platformMesh, False, False)
1109                         self._objectShader.unbind()
1110
1111                 size = [profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')]
1112                 v0 = [ size[0] / 2, size[1] / 2, size[2]]
1113                 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
1114                 v2 = [-size[0] / 2, size[1] / 2, size[2]]
1115                 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
1116                 v4 = [ size[0] / 2, size[1] / 2, 0]
1117                 v5 = [ size[0] / 2,-size[1] / 2, 0]
1118                 v6 = [-size[0] / 2, size[1] / 2, 0]
1119                 v7 = [-size[0] / 2,-size[1] / 2, 0]
1120
1121                 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
1122                 glEnableClientState(GL_VERTEX_ARRAY)
1123                 glVertexPointer(3, GL_FLOAT, 3*4, vList)
1124
1125                 glColor4ub(5, 171, 231, 64)
1126                 glDrawArrays(GL_QUADS, 0, 4)
1127                 glColor4ub(5, 171, 231, 96)
1128                 glDrawArrays(GL_QUADS, 4, 8)
1129                 glColor4ub(5, 171, 231, 128)
1130                 glDrawArrays(GL_QUADS, 12, 8)
1131
1132                 sx = self._machineSize[0]
1133                 sy = self._machineSize[1]
1134                 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
1135                         for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
1136                                 x1 = x * 10
1137                                 x2 = x1 + 10
1138                                 y1 = y * 10
1139                                 y2 = y1 + 10
1140                                 x1 = max(min(x1, sx/2), -sx/2)
1141                                 y1 = max(min(y1, sy/2), -sy/2)
1142                                 x2 = max(min(x2, sx/2), -sx/2)
1143                                 y2 = max(min(y2, sy/2), -sy/2)
1144                                 if (x & 1) == (y & 1):
1145                                         glColor4ub(5, 171, 231, 127)
1146                                 else:
1147                                         glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
1148                                 glBegin(GL_QUADS)
1149                                 glVertex3f(x1, y1, -0.02)
1150                                 glVertex3f(x2, y1, -0.02)
1151                                 glVertex3f(x2, y2, -0.02)
1152                                 glVertex3f(x1, y2, -0.02)
1153                                 glEnd()
1154
1155                 glDisableClientState(GL_VERTEX_ARRAY)
1156                 glDisable(GL_BLEND)
1157                 glDisable(GL_CULL_FACE)
1158
1159         def _generateGCodeVBOs(self, layer):
1160                 ret = []
1161                 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1162                         pointList = numpy.zeros((0,3), numpy.float32)
1163                         for path in layer:
1164                                 if path['type'] == 'extrude' and path['pathType'] == extrudeType:
1165                                         a = path['points']
1166                                         a = numpy.concatenate((a[:-1], a[1:]), 1)
1167                                         a = a.reshape((len(a) * 2, 3))
1168                                         pointList = numpy.concatenate((pointList, a))
1169                         ret.append(opengl.GLVBO(pointList))
1170                 return ret
1171
1172         def _generateGCodeVBOs2(self, layer):
1173                 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
1174                 filamentArea = math.pi * filamentRadius * filamentRadius
1175
1176                 ret = []
1177                 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1178                         pointList = numpy.zeros((0,3), numpy.float32)
1179                         for path in layer:
1180                                 if path['type'] == 'extrude' and path['pathType'] == extrudeType:
1181                                         a = path['points']
1182                                         if extrudeType == 'FILL':
1183                                                 a[:,2] += 0.01
1184
1185                                         normal = a[1:] - a[:-1]
1186                                         lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
1187                                         normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
1188                                         normal[:,2] /= lens
1189
1190                                         ePerDist = path['extrusion'][1:] / lens
1191                                         lineWidth = ePerDist * (filamentArea / path['layerThickness'] / 2)
1192
1193                                         normal[:,0] *= lineWidth
1194                                         normal[:,1] *= lineWidth
1195
1196                                         b = numpy.zeros((len(a)-1, 0), numpy.float32)
1197                                         b = numpy.concatenate((b, a[1:] + normal), 1)
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]), 1)
1202                                         #b = numpy.concatenate((b, a[:-1]), 1)
1203                                         b = b.reshape((len(b) * 4, 3))
1204
1205                                         if len(a) > 2:
1206                                                 normal2 = normal[:-1] + normal[1:]
1207                                                 lens2 = numpy.sqrt(normal2[:,0]**2 + normal2[:,1]**2)
1208                                                 normal2[:,0] /= lens2
1209                                                 normal2[:,1] /= lens2
1210                                                 normal2[:,0] *= lineWidth[:-1]
1211                                                 normal2[:,1] *= lineWidth[:-1]
1212
1213                                                 c = numpy.zeros((len(a)-2, 0), numpy.float32)
1214                                                 c = numpy.concatenate((c, a[1:-1]), 1)
1215                                                 c = numpy.concatenate((c, a[1:-1]+normal[1:]), 1)
1216                                                 c = numpy.concatenate((c, a[1:-1]+normal2), 1)
1217                                                 c = numpy.concatenate((c, a[1:-1]+normal[:-1]), 1)
1218
1219                                                 c = numpy.concatenate((c, a[1:-1]), 1)
1220                                                 c = numpy.concatenate((c, a[1:-1]-normal[1:]), 1)
1221                                                 c = numpy.concatenate((c, a[1:-1]-normal2), 1)
1222                                                 c = numpy.concatenate((c, a[1:-1]-normal[:-1]), 1)
1223
1224                                                 c = c.reshape((len(c) * 8, 3))
1225
1226                                                 pointList = numpy.concatenate((pointList, b, c))
1227                                         else:
1228                                                 pointList = numpy.concatenate((pointList, b))
1229                         ret.append(opengl.GLVBO(pointList))
1230
1231                 pointList = numpy.zeros((0,3), numpy.float32)
1232                 for path in layer:
1233                         if path['type'] == 'move':
1234                                 a = path['points'] + numpy.array([0,0,0.01], numpy.float32)
1235                                 a = numpy.concatenate((a[:-1], a[1:]), 1)
1236                                 a = a.reshape((len(a) * 2, 3))
1237                                 pointList = numpy.concatenate((pointList, a))
1238                         if path['type'] == 'retract':
1239                                 a = path['points'] + numpy.array([0,0,0.01], numpy.float32)
1240                                 a = numpy.concatenate((a[:-1], a[1:] + numpy.array([0,0,1], numpy.float32)), 1)
1241                                 a = a.reshape((len(a) * 2, 3))
1242                                 pointList = numpy.concatenate((pointList, a))
1243                 ret.append(opengl.GLVBO(pointList))
1244
1245                 return ret
1246
1247         def getObjectCenterPos(self):
1248                 if self._selectedObj is None:
1249                         return [0.0, 0.0, 0.0]
1250                 pos = self._selectedObj.getPosition()
1251                 size = self._selectedObj.getSize()
1252                 return [pos[0], pos[1], size[2]/2 - profile.getProfileSettingFloat('object_sink')]
1253
1254         def getObjectBoundaryCircle(self):
1255                 if self._selectedObj is None:
1256                         return 0.0
1257                 return self._selectedObj.getBoundaryCircle()
1258
1259         def getObjectSize(self):
1260                 if self._selectedObj is None:
1261                         return [0.0, 0.0, 0.0]
1262                 return self._selectedObj.getSize()
1263
1264         def getObjectMatrix(self):
1265                 if self._selectedObj is None:
1266                         return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1267                 return self._selectedObj.getMatrix()
1268
1269 class shaderEditor(wx.Dialog):
1270         def __init__(self, parent, callback, v, f):
1271                 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1272                 self._callback = callback
1273                 s = wx.BoxSizer(wx.VERTICAL)
1274                 self.SetSizer(s)
1275                 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1276                 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1277                 s.Add(self._vertex, 1, flag=wx.EXPAND)
1278                 s.Add(self._fragment, 1, flag=wx.EXPAND)
1279
1280                 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1281                 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1282
1283                 self.SetPosition(self.GetParent().GetPosition())
1284                 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1285                 self.Show()
1286
1287         def OnText(self, e):
1288                 self._callback(self._vertex.GetValue(), self._fragment.GetValue())