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