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