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