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