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