chiark / gitweb /
Change how the engine is interfaced from the python code. Put the GCode viewer in...
[cura.git] / Cura / gui / sceneView.py
1 from __future__ import absolute_import
2 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
3
4 import wx
5 import numpy
6 import time
7 import os
8 import traceback
9 import threading
10 import math
11 import platform
12 import cStringIO as StringIO
13
14 import OpenGL
15 #OpenGL.ERROR_CHECKING = False
16 from OpenGL.GLU import *
17 from OpenGL.GL import *
18
19 from Cura.gui import printWindow
20 from Cura.gui import printWindow2
21 from Cura.util import profile
22 from Cura.util import meshLoader
23 from Cura.util import objectScene
24 from Cura.util import resources
25 from Cura.util import sliceEngine
26 from Cura.util import machineCom
27 from Cura.util import removableStorage
28 from Cura.util import explorer
29 from Cura.util.printerConnection import printerConnectionManager
30 from Cura.gui.util import previewTools
31 from Cura.gui.util import opengl
32 from Cura.gui.util import openglGui
33 from Cura.gui.util import engineResultView
34 from Cura.gui.tools import youmagineGui
35 from Cura.gui.tools import imageToMesh
36
37 class SceneView(openglGui.glGuiPanel):
38         def __init__(self, parent):
39                 super(SceneView, self).__init__(parent)
40
41                 self._yaw = 30
42                 self._pitch = 60
43                 self._zoom = 300
44                 self._scene = objectScene.Scene()
45                 self._objectShader = None
46                 self._objectLoadShader = None
47                 self._focusObj = None
48                 self._selectedObj = None
49                 self._objColors = [None,None,None,None]
50                 self._mouseX = -1
51                 self._mouseY = -1
52                 self._mouseState = None
53                 self._viewTarget = numpy.array([0,0,0], numpy.float32)
54                 self._animView = None
55                 self._animZoom = None
56                 self._platformMesh = {}
57                 self._platformTexture = None
58                 self._isSimpleMode = True
59                 self._usbPrintMonitor = printWindow.printProcessMonitor(lambda : self._queueRefresh())
60                 self._printerConnectionManager = printerConnectionManager.PrinterConnectionManager()
61
62                 self._viewport = None
63                 self._modelMatrix = None
64                 self._projMatrix = None
65                 self.tempMatrix = None
66
67                 self.openFileButton      = openglGui.glButton(self, 4, _("Load"), (0,0), self.showLoadModel)
68                 self.printButton         = openglGui.glButton(self, 6, _("Print"), (1,0), self.OnPrintButton)
69                 self.printButton.setDisabled(True)
70
71                 group = []
72                 self.rotateToolButton = openglGui.glRadioButton(self, 8, _("Rotate"), (0,-1), group, self.OnToolSelect)
73                 self.scaleToolButton  = openglGui.glRadioButton(self, 9, _("Scale"), (1,-1), group, self.OnToolSelect)
74                 self.mirrorToolButton  = openglGui.glRadioButton(self, 10, _("Mirror"), (2,-1), group, self.OnToolSelect)
75
76                 self.resetRotationButton = openglGui.glButton(self, 12, _("Reset"), (0,-2), self.OnRotateReset)
77                 self.layFlatButton       = openglGui.glButton(self, 16, _("Lay flat"), (0,-3), self.OnLayFlat)
78
79                 self.resetScaleButton    = openglGui.glButton(self, 13, _("Reset"), (1,-2), self.OnScaleReset)
80                 self.scaleMaxButton      = openglGui.glButton(self, 17, _("To max"), (1,-3), self.OnScaleMax)
81
82                 self.mirrorXButton       = openglGui.glButton(self, 14, _("Mirror X"), (2,-2), lambda button: self.OnMirror(0))
83                 self.mirrorYButton       = openglGui.glButton(self, 18, _("Mirror Y"), (2,-3), lambda button: self.OnMirror(1))
84                 self.mirrorZButton       = openglGui.glButton(self, 22, _("Mirror Z"), (2,-4), lambda button: self.OnMirror(2))
85
86                 self.rotateToolButton.setExpandArrow(True)
87                 self.scaleToolButton.setExpandArrow(True)
88                 self.mirrorToolButton.setExpandArrow(True)
89
90                 self.scaleForm = openglGui.glFrame(self, (2, -2))
91                 openglGui.glGuiLayoutGrid(self.scaleForm)
92                 openglGui.glLabel(self.scaleForm, _("Scale X"), (0,0))
93                 self.scaleXctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,0), lambda value: self.OnScaleEntry(value, 0))
94                 openglGui.glLabel(self.scaleForm, _("Scale Y"), (0,1))
95                 self.scaleYctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,1), lambda value: self.OnScaleEntry(value, 1))
96                 openglGui.glLabel(self.scaleForm, _("Scale Z"), (0,2))
97                 self.scaleZctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,2), lambda value: self.OnScaleEntry(value, 2))
98                 openglGui.glLabel(self.scaleForm, _("Size X (mm)"), (0,4))
99                 self.scaleXmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,4), lambda value: self.OnScaleEntryMM(value, 0))
100                 openglGui.glLabel(self.scaleForm, _("Size Y (mm)"), (0,5))
101                 self.scaleYmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,5), lambda value: self.OnScaleEntryMM(value, 1))
102                 openglGui.glLabel(self.scaleForm, _("Size Z (mm)"), (0,6))
103                 self.scaleZmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,6), lambda value: self.OnScaleEntryMM(value, 2))
104                 openglGui.glLabel(self.scaleForm, _("Uniform scale"), (0,8))
105                 self.scaleUniform = openglGui.glCheckbox(self.scaleForm, True, (1,8), None)
106
107                 self.viewSelection = openglGui.glComboButton(self, _("View mode"), [7,19,11,15,23], [_("Normal"), _("Overhang"), _("Transparent"), _("X-Ray"), _("Layers")], (-1,0), self.OnViewChange)
108
109                 self.youMagineButton = openglGui.glButton(self, 26, _("Share on YouMagine"), (2,0), lambda button: youmagineGui.youmagineManager(self.GetTopLevelParent(), self._scene))
110                 self.youMagineButton.setDisabled(True)
111
112                 self.notification = openglGui.glNotification(self, (0, 0))
113
114                 self._engine = sliceEngine.Engine(self._updateEngineProgress)
115                 self._engineResultView = engineResultView.engineResultView(self)
116                 self._sceneUpdateTimer = wx.Timer(self)
117                 self.Bind(wx.EVT_TIMER, self._onRunEngine, self._sceneUpdateTimer)
118                 self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
119                 self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave)
120
121                 self.OnViewChange()
122                 self.OnToolSelect(0)
123                 self.updateToolButtons()
124                 self.updateProfileToControls()
125
126         def loadGCodeFile(self, filename):
127                 self.OnDeleteAll(None)
128                 #TODO: Load straight GCodeFile
129                 self.printButton.setBottomText('')
130                 self.viewSelection.setValue(4)
131                 self.printButton.setDisabled(False)
132                 self.youMagineButton.setDisabled(True)
133                 self.OnViewChange()
134
135         def loadSceneFiles(self, filenames):
136                 self.youMagineButton.setDisabled(False)
137                 #if self.viewSelection.getValue() == 4:
138                 #       self.viewSelection.setValue(0)
139                 #       self.OnViewChange()
140                 self.loadScene(filenames)
141
142         def loadFiles(self, filenames):
143                 mainWindow = self.GetParent().GetParent().GetParent()
144                 # only one GCODE file can be active
145                 # so if single gcode file, process this
146                 # otherwise ignore all gcode files
147                 gcodeFilename = None
148                 if len(filenames) == 1:
149                         filename = filenames[0]
150                         ext = os.path.splitext(filename)[1].lower()
151                         if ext == '.g' or ext == '.gcode':
152                                 gcodeFilename = filename
153                                 mainWindow.addToModelMRU(filename)
154                 if gcodeFilename is not None:
155                         self.loadGCodeFile(gcodeFilename)
156                 else:
157                         # process directories and special file types
158                         # and keep scene files for later processing
159                         scene_filenames = []
160                         ignored_types = dict()
161                         # use file list as queue
162                         # pop first entry for processing and append new files at end
163                         while filenames:
164                                 filename = filenames.pop(0)
165                                 if os.path.isdir(filename):
166                                         # directory: queue all included files and directories
167                                         filenames.extend(os.path.join(filename, f) for f in os.listdir(filename))
168                                 else:
169                                         ext = os.path.splitext(filename)[1].lower()
170                                         if ext == '.ini':
171                                                 profile.loadProfile(filename)
172                                                 mainWindow.addToProfileMRU(filename)
173                                         elif ext in meshLoader.loadSupportedExtensions() or ext in imageToMesh.supportedExtensions():
174                                                 scene_filenames.append(filename)
175                                                 mainWindow.addToModelMRU(filename)
176                                         else:
177                                                 ignored_types[ext] = 1
178                         if ignored_types:
179                                 ignored_types = ignored_types.keys()
180                                 ignored_types.sort()
181                                 self.notification.message("ignored: " + " ".join("*" + type for type in ignored_types))
182                         mainWindow.updateProfileToAllControls()
183                         # now process all the scene files
184                         if scene_filenames:
185                                 self.loadSceneFiles(scene_filenames)
186                                 self._selectObject(None)
187                                 self.sceneUpdated()
188                                 newZoom = numpy.max(self._machineSize)
189                                 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
190                                 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
191
192         def showLoadModel(self, button = 1):
193                 if button == 1:
194                         dlg=wx.FileDialog(self, _("Open 3D model"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST|wx.FD_MULTIPLE)
195                         dlg.SetWildcard(meshLoader.loadWildcardFilter() + imageToMesh.wildcardList() + "|GCode file (*.gcode)|*.g;*.gcode;*.G;*.GCODE")
196                         if dlg.ShowModal() != wx.ID_OK:
197                                 dlg.Destroy()
198                                 return
199                         filenames = dlg.GetPaths()
200                         dlg.Destroy()
201                         if len(filenames) < 1:
202                                 return False
203                         profile.putPreference('lastFile', filenames[0])
204                         self.loadFiles(filenames)
205
206         def showSaveModel(self):
207                 if len(self._scene.objects()) < 1:
208                         return
209                 dlg=wx.FileDialog(self, _("Save 3D model"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
210                 dlg.SetWildcard(meshLoader.saveWildcardFilter())
211                 if dlg.ShowModal() != wx.ID_OK:
212                         dlg.Destroy()
213                         return
214                 filename = dlg.GetPath()
215                 dlg.Destroy()
216                 meshLoader.saveMeshes(filename, self._scene.objects())
217
218         def OnPrintButton(self, button):
219                 if button == 1:
220                         connectionGroup = self._printerConnectionManager.getAvailableGroup()
221                         if machineCom.machineIsConnected():
222                                 self.showPrintWindow()
223                         elif len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionGroup is None or connectionGroup.getPriority() < 0):
224                                 drives = removableStorage.getPossibleSDcardDrives()
225                                 if len(drives) > 1:
226                                         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))
227                                         if dlg.ShowModal() != wx.ID_OK:
228                                                 dlg.Destroy()
229                                                 return
230                                         drive = drives[dlg.GetSelection()]
231                                         dlg.Destroy()
232                                 else:
233                                         drive = drives[0]
234                                 filename = self._scene._objectList[0].getName() + '.gcode'
235                                 threading.Thread(target=self._saveGCode,args=(drive[1] + filename, drive[1])).start()
236                         elif connectionGroup is not None:
237                                 connections = connectionGroup.getAvailableConnections()
238                                 if len(connections) < 2:
239                                         connection = connections[0]
240                                 else:
241                                         dlg = wx.SingleChoiceDialog(self, "Select the %s connection to use" % (connectionGroup.getName()), "Multiple %s connections found" % (connectionGroup.getName()), map(lambda n: n.getName(), connections))
242                                         if dlg.ShowModal() != wx.ID_OK:
243                                                 dlg.Destroy()
244                                                 return
245                                         connection = connections[dlg.GetSelection()]
246                                         dlg.Destroy()
247                                 self._openPrintWindowForConnection(connection)
248                         else:
249                                 self.showSaveGCode()
250                 if button == 3:
251                         menu = wx.Menu()
252                         self.Bind(wx.EVT_MENU, lambda e: self.showPrintWindow(), menu.Append(-1, _("Print with USB")))
253                         connections = self._printerConnectionManager.getAvailableConnections()
254                         menu.connectionMap = {}
255                         for connection in connections:
256                                 i = menu.Append(-1, _("Print with %s") % (connection.getName()))
257                                 menu.connectionMap[i.GetId()] = connection
258                                 self.Bind(wx.EVT_MENU, lambda e: self._openPrintWindowForConnection(e.GetEventObject().connectionMap[e.GetId()]), i)
259                         self.Bind(wx.EVT_MENU, lambda e: self.showSaveGCode(), menu.Append(-1, _("Save GCode...")))
260                         self.Bind(wx.EVT_MENU, lambda e: self._showEngineLog(), menu.Append(-1, _("Slice engine log...")))
261                         self.PopupMenu(menu)
262                         menu.Destroy()
263
264         def _openPrintWindowForConnection(self, connection):
265                 print '_openPrintWindowForConnection', connection.getName()
266                 if connection.window is None or not connection.window:
267                         connection.window = printWindow2.printWindow(connection)
268                 connection.window.Show()
269                 connection.window.Raise()
270                 #TODO: Fix for _engine.getResult
271                 if not connection.loadFile(self._gcodeFilename):
272                         if connection.isPrinting():
273                                 self.notification.message("Cannot start print, because other print still running.")
274                         else:
275                                 self.notification.message("Failed to start print...")
276
277         def showPrintWindow(self):
278                 if self._gcodeFilename is None:
279                         return
280                 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
281                         wx.MessageBox(_("USB printing on the Ultimaker2 is not supported."), _("USB Printing Error"), wx.OK | wx.ICON_WARNING)
282                         return
283                 #TODO: Fix for _engine.getResult
284                 self._usbPrintMonitor.loadFile(self._gcodeFilename, self._engine.getID())
285                 if self._gcodeFilename is None:
286                         self._engine.submitInfoOnline()
287
288         def showSaveGCode(self):
289                 if len(self._scene._objectList) < 1:
290                         return
291                 dlg=wx.FileDialog(self, _("Save toolpath"), os.path.dirname(profile.getPreference('lastFile')), style=wx.FD_SAVE)
292                 filename = self._scene._objectList[0].getName() + '.gcode'
293                 dlg.SetFilename(filename)
294                 dlg.SetWildcard('Toolpath (*.gcode)|*.gcode;*.g')
295                 if dlg.ShowModal() != wx.ID_OK:
296                         dlg.Destroy()
297                         return
298                 filename = dlg.GetPath()
299                 dlg.Destroy()
300
301                 threading.Thread(target=self._saveGCode,args=(filename,)).start()
302
303         def _saveGCode(self, targetFilename, ejectDrive = False):
304                 data = self._engine.getResult().getGCode()
305                 try:
306                         size = float(len(data))
307                         fsrc = StringIO.StringIO(data)
308                         with open(targetFilename, 'wb') as fdst:
309                                 while 1:
310                                         buf = fsrc.read(16*1024)
311                                         if not buf:
312                                                 break
313                                         fdst.write(buf)
314                                         self.printButton.setProgressBar(float(fsrc.tell()) / size)
315                                         self._queueRefresh()
316                 except:
317                         import sys, traceback
318                         traceback.print_exc()
319                         self.notification.message("Failed to save")
320                 else:
321                         if ejectDrive:
322                                 self.notification.message("Saved as %s" % (targetFilename), lambda : self._doEjectSD(ejectDrive), 31, 'Eject')
323                         elif explorer.hasExplorer():
324                                 self.notification.message("Saved as %s" % (targetFilename), lambda : explorer.openExplorer(targetFilename), 4, 'Open folder')
325                         else:
326                                 self.notification.message("Saved as %s" % (targetFilename))
327                 self.printButton.setProgressBar(None)
328                 self._engine.getResult().submitInfoOnline()
329
330         def _doEjectSD(self, drive):
331                 if removableStorage.ejectDrive(drive):
332                         self.notification.message('You can now eject the card.')
333                 else:
334                         self.notification.message('Safe remove failed...')
335
336         def _showEngineLog(self):
337                 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)
338                 dlg.ShowModal()
339                 dlg.Destroy()
340
341         def OnToolSelect(self, button):
342                 if self.rotateToolButton.getSelected():
343                         self.tool = previewTools.toolRotate(self)
344                 elif self.scaleToolButton.getSelected():
345                         self.tool = previewTools.toolScale(self)
346                 elif self.mirrorToolButton.getSelected():
347                         self.tool = previewTools.toolNone(self)
348                 else:
349                         self.tool = previewTools.toolNone(self)
350                 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
351                 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
352                 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
353                 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
354                 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
355                 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
356                 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
357                 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
358
359         def updateToolButtons(self):
360                 if self._selectedObj is None:
361                         hidden = True
362                 else:
363                         hidden = False
364                 self.rotateToolButton.setHidden(hidden)
365                 self.scaleToolButton.setHidden(hidden)
366                 self.mirrorToolButton.setHidden(hidden)
367                 if hidden:
368                         self.rotateToolButton.setSelected(False)
369                         self.scaleToolButton.setSelected(False)
370                         self.mirrorToolButton.setSelected(False)
371                         self.OnToolSelect(0)
372
373         def OnViewChange(self):
374                 if self.viewSelection.getValue() == 4:
375                         self.viewMode = 'gcode'
376                 elif self.viewSelection.getValue() == 1:
377                         self.viewMode = 'overhang'
378                 elif self.viewSelection.getValue() == 2:
379                         self.viewMode = 'transparent'
380                 elif self.viewSelection.getValue() == 3:
381                         self.viewMode = 'xray'
382                 else:
383                         self.viewMode = 'normal'
384                 self._engineResultView.setEnabled(self.viewMode == 'gcode')
385                 self.QueueRefresh()
386
387         def OnRotateReset(self, button):
388                 if self._selectedObj is None:
389                         return
390                 self._selectedObj.resetRotation()
391                 self._scene.pushFree(self._selectedObj)
392                 self._selectObject(self._selectedObj)
393                 self.sceneUpdated()
394
395         def OnLayFlat(self, button):
396                 if self._selectedObj is None:
397                         return
398                 self._selectedObj.layFlat()
399                 self._scene.pushFree(self._selectedObj)
400                 self._selectObject(self._selectedObj)
401                 self.sceneUpdated()
402
403         def OnScaleReset(self, button):
404                 if self._selectedObj is None:
405                         return
406                 self._selectedObj.resetScale()
407                 self._selectObject(self._selectedObj)
408                 self.updateProfileToControls()
409                 self.sceneUpdated()
410
411         def OnScaleMax(self, button):
412                 if self._selectedObj is None:
413                         return
414                 machine = profile.getMachineSetting('machine_type')
415                 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
416                 self._scene.pushFree(self._selectedObj)
417                 #self.sceneUpdated()
418                 if machine == "ultimaker2":
419                         #This is bad and Jaime should feel bad!
420                         self._selectedObj.setPosition(numpy.array([0.0,-10.0]))
421                         self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([1,1,1], numpy.float32))
422                         self._selectedObj.setPosition(numpy.array([0.0,0.0]))
423                         self._scene.pushFree(self._selectedObj)
424                 else:
425                         self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
426                         self._scene.pushFree(self._selectedObj)
427                         self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([1,1,1], numpy.float32))
428                 self._scene.pushFree(self._selectedObj)
429                 self._selectObject(self._selectedObj)
430                 self.updateProfileToControls()
431                 self.sceneUpdated()
432
433         def OnMirror(self, axis):
434                 if self._selectedObj is None:
435                         return
436                 self._selectedObj.mirror(axis)
437                 self.sceneUpdated()
438
439         def OnScaleEntry(self, value, axis):
440                 if self._selectedObj is None:
441                         return
442                 try:
443                         value = float(value)
444                 except:
445                         return
446                 self._selectedObj.setScale(value, axis, self.scaleUniform.getValue())
447                 self.updateProfileToControls()
448                 self._scene.pushFree(self._selectedObj)
449                 self._selectObject(self._selectedObj)
450                 self.sceneUpdated()
451
452         def OnScaleEntryMM(self, value, axis):
453                 if self._selectedObj is None:
454                         return
455                 try:
456                         value = float(value)
457                 except:
458                         return
459                 self._selectedObj.setSize(value, axis, self.scaleUniform.getValue())
460                 self.updateProfileToControls()
461                 self._scene.pushFree(self._selectedObj)
462                 self._selectObject(self._selectedObj)
463                 self.sceneUpdated()
464
465         def OnDeleteAll(self, e):
466                 while len(self._scene.objects()) > 0:
467                         self._deleteObject(self._scene.objects()[0])
468                 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
469
470         def OnMultiply(self, e):
471                 if self._focusObj is None:
472                         return
473                 obj = self._focusObj
474                 dlg = wx.NumberEntryDialog(self, _("How many copies do you want?"), _("Number of copies"), _("Multiply"), 1, 1, 100)
475                 if dlg.ShowModal() != wx.ID_OK:
476                         dlg.Destroy()
477                         return
478                 cnt = dlg.GetValue()
479                 dlg.Destroy()
480                 n = 0
481                 while True:
482                         n += 1
483                         newObj = obj.copy()
484                         self._scene.add(newObj)
485                         self._scene.centerAll()
486                         if not self._scene.checkPlatform(newObj):
487                                 break
488                         if n > cnt:
489                                 break
490                 if n <= cnt:
491                         self.notification.message("Could not create more then %d items" % (n - 1))
492                 self._scene.remove(newObj)
493                 self._scene.centerAll()
494                 self.sceneUpdated()
495
496         def OnSplitObject(self, e):
497                 if self._focusObj is None:
498                         return
499                 self._scene.remove(self._focusObj)
500                 for obj in self._focusObj.split(self._splitCallback):
501                         if numpy.max(obj.getSize()) > 2.0:
502                                 self._scene.add(obj)
503                 self._scene.centerAll()
504                 self._selectObject(None)
505                 self.sceneUpdated()
506
507         def OnCenter(self, e):
508                 if self._focusObj is None:
509                         return
510                 self._focusObj.setPosition(numpy.array([0.0, 0.0]))
511                 self._scene.pushFree(self._selectedObj)
512                 newViewPos = numpy.array([self._focusObj.getPosition()[0], self._focusObj.getPosition()[1], self._focusObj.getSize()[2] / 2])
513                 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
514                 self.sceneUpdated()
515
516         def _splitCallback(self, progress):
517                 print progress
518
519         def OnMergeObjects(self, e):
520                 if self._selectedObj is None or self._focusObj is None or self._selectedObj == self._focusObj:
521                         if len(self._scene.objects()) == 2:
522                                 self._scene.merge(self._scene.objects()[0], self._scene.objects()[1])
523                                 self.sceneUpdated()
524                         return
525                 self._scene.merge(self._selectedObj, self._focusObj)
526                 self.sceneUpdated()
527
528         def sceneUpdated(self):
529                 self._sceneUpdateTimer.Start(500, True)
530                 self._engine.abortEngine()
531                 self._scene.updateSizeOffsets()
532                 self.QueueRefresh()
533
534         def _onRunEngine(self, e):
535                 if self._isSimpleMode:
536                         self.GetTopLevelParent().simpleSettingsPanel.setupSlice()
537                 self._engine.runEngine(self._scene)
538                 if self._isSimpleMode:
539                         profile.resetTempOverride()
540
541         def _updateEngineProgress(self, progressValue):
542                 result = self._engine.getResult()
543                 finished = result is not None and result.isFinished()
544                 if not finished:
545                         if self.printButton.getProgressBar() is not None and progressValue >= 0.0 and abs(self.printButton.getProgressBar() - progressValue) < 0.01:
546                                 return
547                 self.printButton.setDisabled(not finished)
548                 if progressValue >= 0.0:
549                         self.printButton.setProgressBar(progressValue)
550                 else:
551                         self.printButton.setProgressBar(None)
552                 self._engineResultView.setResult(result)
553                 if finished:
554                         self.printButton.setProgressBar(None)
555                         text = '%s' % (result.getPrintTime())
556                         for e in xrange(0, int(profile.getMachineSetting('extruder_amount'))):
557                                 amount = result.getFilamentAmount(e)
558                                 if amount is None:
559                                         continue
560                                 text += '\n%s' % (amount)
561                                 cost = result.getFilamentCost(e)
562                                 if cost is not None:
563                                         text += '\n%s' % (cost)
564                         self.printButton.setBottomText(text)
565                 else:
566                         self.printButton.setBottomText('')
567                 self.QueueRefresh()
568
569         def loadScene(self, fileList):
570                 for filename in fileList:
571                         try:
572                                 ext = os.path.splitext(filename)[1].lower()
573                                 if ext in imageToMesh.supportedExtensions():
574                                         imageToMesh.convertImageDialog(self, filename).Show()
575                                         objList = []
576                                 else:
577                                         objList = meshLoader.loadMeshes(filename)
578                         except:
579                                 traceback.print_exc()
580                         else:
581                                 for obj in objList:
582                                         if self._objectLoadShader is not None:
583                                                 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
584                                         else:
585                                                 obj._loadAnim = None
586                                         self._scene.add(obj)
587                                         if not self._scene.checkPlatform(obj):
588                                                 self._scene.centerAll()
589                                         self._selectObject(obj)
590                                         if obj.getScale()[0] < 1.0:
591                                                 self.notification.message("Warning: Object scaled down.")
592                 self.sceneUpdated()
593
594         def _deleteObject(self, obj):
595                 if obj == self._selectedObj:
596                         self._selectObject(None)
597                 if obj == self._focusObj:
598                         self._focusObj = None
599                 self._scene.remove(obj)
600                 for m in obj._meshList:
601                         if m.vbo is not None and m.vbo.decRef():
602                                 self.glReleaseList.append(m.vbo)
603                 import gc
604                 gc.collect()
605                 self.sceneUpdated()
606
607         def _selectObject(self, obj, zoom = True):
608                 if obj != self._selectedObj:
609                         self._selectedObj = obj
610                         self.updateModelSettingsToControls()
611                         self.updateToolButtons()
612                 if zoom and obj is not None:
613                         newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2])
614                         self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
615                         newZoom = obj.getBoundaryCircle() * 6
616                         if newZoom > numpy.max(self._machineSize) * 3:
617                                 newZoom = numpy.max(self._machineSize) * 3
618                         self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
619
620         def updateProfileToControls(self):
621                 oldSimpleMode = self._isSimpleMode
622                 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
623                 if self._isSimpleMode != oldSimpleMode:
624                         self._scene.arrangeAll()
625                         self.sceneUpdated()
626                 self._scene.updateSizeOffsets(True)
627                 self._machineSize = numpy.array([profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')])
628                 self._objColors[0] = profile.getPreferenceColour('model_colour')
629                 self._objColors[1] = profile.getPreferenceColour('model_colour2')
630                 self._objColors[2] = profile.getPreferenceColour('model_colour3')
631                 self._objColors[3] = profile.getPreferenceColour('model_colour4')
632                 self._scene.updateMachineDimensions()
633                 self.updateModelSettingsToControls()
634
635         def updateModelSettingsToControls(self):
636                 if self._selectedObj is not None:
637                         scale = self._selectedObj.getScale()
638                         size = self._selectedObj.getSize()
639                         self.scaleXctrl.setValue(round(scale[0], 2))
640                         self.scaleYctrl.setValue(round(scale[1], 2))
641                         self.scaleZctrl.setValue(round(scale[2], 2))
642                         self.scaleXmmctrl.setValue(round(size[0], 2))
643                         self.scaleYmmctrl.setValue(round(size[1], 2))
644                         self.scaleZmmctrl.setValue(round(size[2], 2))
645
646         def OnKeyChar(self, keyCode):
647                 if self._engineResultView.OnKeyChar(keyCode):
648                         return
649                 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE or (keyCode == wx.WXK_BACK and platform.system() == "Darwin"):
650                         if self._selectedObj is not None:
651                                 self._deleteObject(self._selectedObj)
652                                 self.QueueRefresh()
653                 if keyCode == wx.WXK_UP:
654                         if wx.GetKeyState(wx.WXK_SHIFT):
655                                 self._zoom /= 1.2
656                                 if self._zoom < 1:
657                                         self._zoom = 1
658                         else:
659                                 self._pitch -= 15
660                         self.QueueRefresh()
661                 elif keyCode == wx.WXK_DOWN:
662                         if wx.GetKeyState(wx.WXK_SHIFT):
663                                 self._zoom *= 1.2
664                                 if self._zoom > numpy.max(self._machineSize) * 3:
665                                         self._zoom = numpy.max(self._machineSize) * 3
666                         else:
667                                 self._pitch += 15
668                         self.QueueRefresh()
669                 elif keyCode == wx.WXK_LEFT:
670                         self._yaw -= 15
671                         self.QueueRefresh()
672                 elif keyCode == wx.WXK_RIGHT:
673                         self._yaw += 15
674                         self.QueueRefresh()
675                 elif keyCode == wx.WXK_NUMPAD_ADD or keyCode == wx.WXK_ADD or keyCode == ord('+') or keyCode == ord('='):
676                         self._zoom /= 1.2
677                         if self._zoom < 1:
678                                 self._zoom = 1
679                         self.QueueRefresh()
680                 elif keyCode == wx.WXK_NUMPAD_SUBTRACT or keyCode == wx.WXK_SUBTRACT or keyCode == ord('-'):
681                         self._zoom *= 1.2
682                         if self._zoom > numpy.max(self._machineSize) * 3:
683                                 self._zoom = numpy.max(self._machineSize) * 3
684                         self.QueueRefresh()
685                 elif keyCode == wx.WXK_HOME:
686                         self._yaw = 30
687                         self._pitch = 60
688                         self.QueueRefresh()
689                 elif keyCode == wx.WXK_PAGEUP:
690                         self._yaw = 0
691                         self._pitch = 0
692                         self.QueueRefresh()
693                 elif keyCode == wx.WXK_PAGEDOWN:
694                         self._yaw = 0
695                         self._pitch = 90
696                         self.QueueRefresh()
697                 elif keyCode == wx.WXK_END:
698                         self._yaw = 90
699                         self._pitch = 90
700                         self.QueueRefresh()
701
702                 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
703                         shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
704                 if keyCode == wx.WXK_F4 and wx.GetKeyState(wx.WXK_SHIFT):
705                         from collections import defaultdict
706                         from gc import get_objects
707                         self._beforeLeakTest = defaultdict(int)
708                         for i in get_objects():
709                                 self._beforeLeakTest[type(i)] += 1
710                 if keyCode == wx.WXK_F5 and wx.GetKeyState(wx.WXK_SHIFT):
711                         from collections import defaultdict
712                         from gc import get_objects
713                         self._afterLeakTest = defaultdict(int)
714                         for i in get_objects():
715                                 self._afterLeakTest[type(i)] += 1
716                         for k in self._afterLeakTest:
717                                 if self._afterLeakTest[k]-self._beforeLeakTest[k]:
718                                         print k, self._afterLeakTest[k], self._beforeLeakTest[k], self._afterLeakTest[k] - self._beforeLeakTest[k]
719
720         def ShaderUpdate(self, v, f):
721                 s = opengl.GLShader(v, f)
722                 if s.isValid():
723                         self._objectLoadShader.release()
724                         self._objectLoadShader = s
725                         for obj in self._scene.objects():
726                                 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
727                         self.QueueRefresh()
728
729         def OnMouseDown(self,e):
730                 self._mouseX = e.GetX()
731                 self._mouseY = e.GetY()
732                 self._mouseClick3DPos = self._mouse3Dpos
733                 self._mouseClickFocus = self._focusObj
734                 if e.ButtonDClick():
735                         self._mouseState = 'doubleClick'
736                 else:
737                         self._mouseState = 'dragOrClick'
738                 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
739                 p0 -= self.getObjectCenterPos() - self._viewTarget
740                 p1 -= self.getObjectCenterPos() - self._viewTarget
741                 if self.tool.OnDragStart(p0, p1):
742                         self._mouseState = 'tool'
743                 if self._mouseState == 'dragOrClick':
744                         if e.GetButton() == 1:
745                                 if self._focusObj is not None:
746                                         self._selectObject(self._focusObj, False)
747                                         self.QueueRefresh()
748
749         def OnMouseUp(self, e):
750                 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
751                         return
752                 if self._mouseState == 'dragOrClick':
753                         if e.GetButton() == 1:
754                                 self._selectObject(self._focusObj)
755                         if e.GetButton() == 3:
756                                         menu = wx.Menu()
757                                         if self._focusObj is not None:
758                                                 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, _("Delete object")))
759                                                 self.Bind(wx.EVT_MENU, self.OnCenter, menu.Append(-1, _("Center on platform")))
760                                                 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, _("Multiply object")))
761                                                 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, _("Split object into parts")))
762                                         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:
763                                                 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, _("Dual extrusion merge")))
764                                         if len(self._scene.objects()) > 0:
765                                                 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, _("Delete all objects")))
766                                         if menu.MenuItemCount > 0:
767                                                 self.PopupMenu(menu)
768                                         menu.Destroy()
769                 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
770                         self._scene.pushFree(self._selectedObj)
771                         self.sceneUpdated()
772                 elif self._mouseState == 'tool':
773                         if self.tempMatrix is not None and self._selectedObj is not None:
774                                 self._selectedObj.applyMatrix(self.tempMatrix)
775                                 self._scene.pushFree(self._selectedObj)
776                                 self._selectObject(self._selectedObj)
777                         self.tempMatrix = None
778                         self.tool.OnDragEnd()
779                         self.sceneUpdated()
780                 self._mouseState = None
781
782         def OnMouseMotion(self,e):
783                 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
784                 p0 -= self.getObjectCenterPos() - self._viewTarget
785                 p1 -= self.getObjectCenterPos() - self._viewTarget
786
787                 if e.Dragging() and self._mouseState is not None:
788                         if self._mouseState == 'tool':
789                                 self.tool.OnDrag(p0, p1)
790                         elif not e.LeftIsDown() and e.RightIsDown():
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                                 self._mouseState = 'drag'
808                                 self._zoom += e.GetY() - self._mouseY
809                                 if self._zoom < 1:
810                                         self._zoom = 1
811                                 if self._zoom > numpy.max(self._machineSize) * 3:
812                                         self._zoom = numpy.max(self._machineSize) * 3
813                         elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus:
814                                 self._mouseState = 'dragObject'
815                                 z = max(0, self._mouseClick3DPos[2])
816                                 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
817                                 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
818                                 p0[2] -= z
819                                 p1[2] -= z
820                                 p2[2] -= z
821                                 p3[2] -= z
822                                 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
823                                 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
824                                 diff = cursorZ1 - cursorZ0
825                                 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
826                 if not e.Dragging() or self._mouseState != 'tool':
827                         self.tool.OnMouseMove(p0, p1)
828
829                 self._mouseX = e.GetX()
830                 self._mouseY = e.GetY()
831
832         def OnMouseWheel(self, e):
833                 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
834                 delta = max(min(delta,4),-4)
835                 self._zoom *= 1.0 - delta / 10.0
836                 if self._zoom < 1.0:
837                         self._zoom = 1.0
838                 if self._zoom > numpy.max(self._machineSize) * 3:
839                         self._zoom = numpy.max(self._machineSize) * 3
840                 self.Refresh()
841
842         def OnMouseLeave(self, e):
843                 #self._mouseX = -1
844                 pass
845
846         def getMouseRay(self, x, y):
847                 if self._viewport is None:
848                         return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
849                 p0 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
850                 p1 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
851                 p0 -= self._viewTarget
852                 p1 -= self._viewTarget
853                 return p0, p1
854
855         def _init3DView(self):
856                 # set viewing projection
857                 size = self.GetSize()
858                 glViewport(0, 0, size.GetWidth(), size.GetHeight())
859                 glLoadIdentity()
860
861                 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
862
863                 glDisable(GL_RESCALE_NORMAL)
864                 glDisable(GL_LIGHTING)
865                 glDisable(GL_LIGHT0)
866                 glEnable(GL_DEPTH_TEST)
867                 glDisable(GL_CULL_FACE)
868                 glDisable(GL_BLEND)
869                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
870
871                 glClearColor(0.8, 0.8, 0.8, 1.0)
872                 glClearStencil(0)
873                 glClearDepth(1.0)
874
875                 glMatrixMode(GL_PROJECTION)
876                 glLoadIdentity()
877                 aspect = float(size.GetWidth()) / float(size.GetHeight())
878                 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
879
880                 glMatrixMode(GL_MODELVIEW)
881                 glLoadIdentity()
882                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
883
884         def OnPaint(self,e):
885                 connectionGroup = self._printerConnectionManager.getAvailableGroup()
886                 if machineCom.machineIsConnected():
887                         self.printButton._imageID = 6
888                         self.printButton._tooltip = _("Print")
889                 elif 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 opengl.hasShaderSupport():
909                                 self._objectShader = opengl.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 = opengl.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 = opengl.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 = opengl.GLFakeShader()
985                                 self._objectOverhangShader = opengl.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 = opengl.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)
1082                                 glDisable(GL_DEPTH_TEST)
1083                                 for i in xrange(2, 15, 2):
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):
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
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():
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 opengl.hasShaderSupport():
1204                         glDisable(GL_DEPTH_TEST)
1205                         glPushMatrix()
1206                         glLoadIdentity()
1207                         glTranslate(0,-4,-10)
1208                         glColor4ub(60,60,60,255)
1209                         opengl.glDrawStringCenter(_("Overhang view not working due to lack of OpenGL shaders support."))
1210                         glPopMatrix()
1211
1212         def _renderObject(self, obj, brightness = False, 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                         tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
1221                         glMultMatrixf(tempMatrix)
1222
1223                 offset = obj.getDrawOffset()
1224                 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
1225
1226                 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
1227                 glMultMatrixf(tempMatrix)
1228
1229                 n = 0
1230                 for m in obj._meshList:
1231                         if m.vbo is None:
1232                                 m.vbo = opengl.GLVBO(GL_TRIANGLES, m.vertexes, m.normal)
1233                         if brightness:
1234                                 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
1235                                 n += 1
1236                         m.vbo.render()
1237                 glPopMatrix()
1238
1239         def _drawMachine(self):
1240                 glEnable(GL_CULL_FACE)
1241                 glEnable(GL_BLEND)
1242
1243                 size = [profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')]
1244
1245                 machine = profile.getMachineSetting('machine_type')
1246                 if machine.startswith('ultimaker'):
1247                         if machine not in self._platformMesh:
1248                                 meshes = meshLoader.loadMeshes(resources.getPathForMesh(machine + '_platform.stl'))
1249                                 if len(meshes) > 0:
1250                                         self._platformMesh[machine] = meshes[0]
1251                                 else:
1252                                         self._platformMesh[machine] = None
1253                                 if machine == 'ultimaker2':
1254                                         self._platformMesh[machine]._drawOffset = numpy.array([0,-37,145], numpy.float32)
1255                                 else:
1256                                         self._platformMesh[machine]._drawOffset = numpy.array([0,0,2.5], numpy.float32)
1257                         glColor4f(1,1,1,0.5)
1258                         self._objectShader.bind()
1259                         self._renderObject(self._platformMesh[machine], False, False)
1260                         self._objectShader.unbind()
1261
1262                         #For the Ultimaker 2 render the texture on the back plate to show the Ultimaker2 text.
1263                         if machine == 'ultimaker2':
1264                                 if not hasattr(self._platformMesh[machine], 'texture'):
1265                                         self._platformMesh[machine].texture = opengl.loadGLTexture('Ultimaker2backplate.png')
1266                                 glBindTexture(GL_TEXTURE_2D, self._platformMesh[machine].texture)
1267                                 glEnable(GL_TEXTURE_2D)
1268                                 glPushMatrix()
1269                                 glColor4f(1,1,1,1)
1270
1271                                 glTranslate(0,150,-5)
1272                                 h = 50
1273                                 d = 8
1274                                 w = 100
1275                                 glEnable(GL_BLEND)
1276                                 glBlendFunc(GL_DST_COLOR, GL_ZERO)
1277                                 glBegin(GL_QUADS)
1278                                 glTexCoord2f(1, 0)
1279                                 glVertex3f( w, 0, h)
1280                                 glTexCoord2f(0, 0)
1281                                 glVertex3f(-w, 0, h)
1282                                 glTexCoord2f(0, 1)
1283                                 glVertex3f(-w, 0, 0)
1284                                 glTexCoord2f(1, 1)
1285                                 glVertex3f( w, 0, 0)
1286
1287                                 glTexCoord2f(1, 0)
1288                                 glVertex3f(-w, d, h)
1289                                 glTexCoord2f(0, 0)
1290                                 glVertex3f( w, d, h)
1291                                 glTexCoord2f(0, 1)
1292                                 glVertex3f( w, d, 0)
1293                                 glTexCoord2f(1, 1)
1294                                 glVertex3f(-w, d, 0)
1295                                 glEnd()
1296                                 glDisable(GL_TEXTURE_2D)
1297                                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1298                                 glPopMatrix()
1299                 else:
1300                         glColor4f(0,0,0,1)
1301                         glLineWidth(3)
1302                         glBegin(GL_LINES)
1303                         glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1304                         glVertex3f(-size[0] / 2, -size[1] / 2, 10)
1305                         glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1306                         glVertex3f(-size[0] / 2+10, -size[1] / 2, 0)
1307                         glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1308                         glVertex3f(-size[0] / 2, -size[1] / 2+10, 0)
1309                         glEnd()
1310
1311                 glDepthMask(False)
1312
1313                 polys = profile.getMachineSizePolygons()
1314                 height = profile.getMachineSettingFloat('machine_height')
1315                 circular = profile.getMachineSetting('machine_shape') == 'Circular'
1316                 glBegin(GL_QUADS)
1317                 for n in xrange(0, len(polys[0])):
1318                         if not circular:
1319                                 if n % 2 == 0:
1320                                         glColor4ub(5, 171, 231, 96)
1321                                 else:
1322                                         glColor4ub(5, 171, 231, 64)
1323                         else:
1324                                 glColor4ub(5, 171, 231, 96)
1325
1326                         glVertex3f(polys[0][n][0], polys[0][n][1], height)
1327                         glVertex3f(polys[0][n][0], polys[0][n][1], 0)
1328                         glVertex3f(polys[0][n-1][0], polys[0][n-1][1], 0)
1329                         glVertex3f(polys[0][n-1][0], polys[0][n-1][1], height)
1330                 glEnd()
1331                 glColor4ub(5, 171, 231, 128)
1332                 glBegin(GL_TRIANGLE_FAN)
1333                 for p in polys[0][::-1]:
1334                         glVertex3f(p[0], p[1], height)
1335                 glEnd()
1336
1337                 #Draw checkerboard
1338                 if self._platformTexture is None:
1339                         self._platformTexture = opengl.loadGLTexture('checkerboard.png')
1340                         glBindTexture(GL_TEXTURE_2D, self._platformTexture)
1341                         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
1342                         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
1343                 glColor4f(1,1,1,0.5)
1344                 glBindTexture(GL_TEXTURE_2D, self._platformTexture)
1345                 glEnable(GL_TEXTURE_2D)
1346                 glBegin(GL_TRIANGLE_FAN)
1347                 for p in polys[0]:
1348                         glTexCoord2f(p[0]/20, p[1]/20)
1349                         glVertex3f(p[0], p[1], 0)
1350                 glEnd()
1351                 glDisable(GL_TEXTURE_2D)
1352                 glColor4ub(127, 127, 127, 200)
1353                 for poly in polys[1:]:
1354                         glBegin(GL_TRIANGLE_FAN)
1355                         for p in poly:
1356                                 glTexCoord2f(p[0]/20, p[1]/20)
1357                                 glVertex3f(p[0], p[1], 0)
1358                         glEnd()
1359
1360                 glDepthMask(True)
1361                 glDisable(GL_BLEND)
1362                 glDisable(GL_CULL_FACE)
1363
1364         def getObjectCenterPos(self):
1365                 if self._selectedObj is None:
1366                         return [0.0, 0.0, 0.0]
1367                 pos = self._selectedObj.getPosition()
1368                 size = self._selectedObj.getSize()
1369                 return [pos[0], pos[1], size[2]/2 - profile.getProfileSettingFloat('object_sink')]
1370
1371         def getObjectBoundaryCircle(self):
1372                 if self._selectedObj is None:
1373                         return 0.0
1374                 return self._selectedObj.getBoundaryCircle()
1375
1376         def getObjectSize(self):
1377                 if self._selectedObj is None:
1378                         return [0.0, 0.0, 0.0]
1379                 return self._selectedObj.getSize()
1380
1381         def getObjectMatrix(self):
1382                 if self._selectedObj is None:
1383                         return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1384                 return self._selectedObj.getMatrix()
1385
1386 class shaderEditor(wx.Dialog):
1387         def __init__(self, parent, callback, v, f):
1388                 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1389                 self._callback = callback
1390                 s = wx.BoxSizer(wx.VERTICAL)
1391                 self.SetSizer(s)
1392                 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1393                 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1394                 s.Add(self._vertex, 1, flag=wx.EXPAND)
1395                 s.Add(self._fragment, 1, flag=wx.EXPAND)
1396
1397                 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1398                 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1399
1400                 self.SetPosition(self.GetParent().GetPosition())
1401                 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1402                 self.Show()
1403
1404         def OnText(self, e):
1405                 self._callback(self._vertex.GetValue(), self._fragment.GetValue())