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