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