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