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