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