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