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