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