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