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