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