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