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