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