chiark / gitweb /
Fix loading of files on startup.
[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]) < 6:
894                                                         self._gcodeVBOs[n] += self._generateGCodeVBOs2(self._gcode.layerList[n])
895                                                 glColor3f(c, 0, 0)
896                                                 self._gcodeVBOs[n][5].render(GL_QUADS)
897                                                 glColor3f(0, c, 0)
898                                                 self._gcodeVBOs[n][6].render(GL_QUADS)
899                                                 glColor3f(c/2, c/2, 0.0)
900                                                 self._gcodeVBOs[n][7].render(GL_QUADS)
901                                                 glColor3f(0, c, c)
902                                                 self._gcodeVBOs[n][8].render(GL_QUADS)
903                                                 self._gcodeVBOs[n][9].render(GL_QUADS)
904                                                 glColor3f(0, 0, c)
905                                                 self._gcodeVBOs[n][10].render(GL_LINES)
906                                         else:
907                                                 glColor3f(c, 0, 0)
908                                                 self._gcodeVBOs[n][0].render(GL_LINES)
909                                                 glColor3f(0, c, 0)
910                                                 self._gcodeVBOs[n][1].render(GL_LINES)
911                                                 glColor3f(c/2, c/2, 0.0)
912                                                 self._gcodeVBOs[n][2].render(GL_LINES)
913                                                 glColor3f(0, c, c)
914                                                 self._gcodeVBOs[n][3].render(GL_LINES)
915                                                 self._gcodeVBOs[n][4].render(GL_LINES)
916                                 glPopMatrix()
917                 else:
918                         glStencilFunc(GL_ALWAYS, 1, 1)
919                         glStencilOp(GL_INCR, GL_INCR, GL_INCR)
920
921                         if self.viewMode == 'overhang':
922                                 self._objectOverhangShader.bind()
923                                 self._objectOverhangShader.setUniform('cosAngle', math.cos(math.radians(90 - 60)))
924                         else:
925                                 self._objectShader.bind()
926                         for obj in self._scene.objects():
927                                 if obj._loadAnim is not None:
928                                         if obj._loadAnim.isDone():
929                                                 obj._loadAnim = None
930                                         else:
931                                                 continue
932                                 brightness = 1.0
933                                 if self._focusObj == obj:
934                                         brightness = 1.2
935                                 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
936                                         brightness = 0.8
937
938                                 if self._selectedObj == obj or self._selectedObj is None:
939                                         #If we want transparent, then first render a solid black model to remove the printer size lines.
940                                         if self.viewMode == 'transparent':
941                                                 glColor4f(0, 0, 0, 0)
942                                                 self._renderObject(obj)
943                                                 glEnable(GL_BLEND)
944                                                 glBlendFunc(GL_ONE, GL_ONE)
945                                                 glDisable(GL_DEPTH_TEST)
946                                                 brightness *= 0.5
947                                         if self.viewMode == 'xray':
948                                                 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
949                                         glStencilOp(GL_INCR, GL_INCR, GL_INCR)
950                                         glEnable(GL_STENCIL_TEST)
951
952                                 if self.viewMode == 'overhang':
953                                         if self._selectedObj == obj and self.tempMatrix is not None:
954                                                 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix() * self.tempMatrix)
955                                         else:
956                                                 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix())
957
958                                 if not self._scene.checkPlatform(obj):
959                                         glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
960                                         self._renderObject(obj)
961                                 else:
962                                         self._renderObject(obj, brightness)
963                                 glDisable(GL_STENCIL_TEST)
964                                 glDisable(GL_BLEND)
965                                 glEnable(GL_DEPTH_TEST)
966                                 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
967
968                         if self.viewMode == 'xray':
969                                 glPushMatrix()
970                                 glLoadIdentity()
971                                 glEnable(GL_STENCIL_TEST)
972                                 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
973                                 glDisable(GL_DEPTH_TEST)
974                                 for i in xrange(2, 15, 2):
975                                         glStencilFunc(GL_EQUAL, i, 0xFF)
976                                         glColor(float(i)/10, float(i)/10, float(i)/5)
977                                         glBegin(GL_QUADS)
978                                         glVertex3f(-1000,-1000,-10)
979                                         glVertex3f( 1000,-1000,-10)
980                                         glVertex3f( 1000, 1000,-10)
981                                         glVertex3f(-1000, 1000,-10)
982                                         glEnd()
983                                 for i in xrange(1, 15, 2):
984                                         glStencilFunc(GL_EQUAL, i, 0xFF)
985                                         glColor(float(i)/10, 0, 0)
986                                         glBegin(GL_QUADS)
987                                         glVertex3f(-1000,-1000,-10)
988                                         glVertex3f( 1000,-1000,-10)
989                                         glVertex3f( 1000, 1000,-10)
990                                         glVertex3f(-1000, 1000,-10)
991                                         glEnd()
992                                 glPopMatrix()
993                                 glDisable(GL_STENCIL_TEST)
994                                 glEnable(GL_DEPTH_TEST)
995
996                         self._objectShader.unbind()
997
998                         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
999                         glEnable(GL_BLEND)
1000                         if self._objectLoadShader is not None:
1001                                 self._objectLoadShader.bind()
1002                                 glColor4f(0.2, 0.6, 1.0, 1.0)
1003                                 for obj in self._scene.objects():
1004                                         if obj._loadAnim is None:
1005                                                 continue
1006                                         self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
1007                                         self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
1008                                         self._renderObject(obj)
1009                                 self._objectLoadShader.unbind()
1010                                 glDisable(GL_BLEND)
1011
1012                 self._drawMachine()
1013
1014                 if self.viewMode == 'gcode':
1015                         if self._gcodeLoadThread is not None and self._gcodeLoadThread.isAlive():
1016                                 glDisable(GL_DEPTH_TEST)
1017                                 glPushMatrix()
1018                                 glLoadIdentity()
1019                                 glTranslate(0,-4,-10)
1020                                 glColor4ub(60,60,60,255)
1021                                 opengl.glDrawStringCenter('Loading toolpath for visualization...')
1022                                 glPopMatrix()
1023                 else:
1024                         #Draw the object box-shadow, so you can see where it will collide with other objects.
1025                         if self._selectedObj is not None and len(self._scene.objects()) > 1:
1026                                 size = self._selectedObj.getSize()[0:2] / 2 + self._scene.getObjectExtend()
1027                                 glPushMatrix()
1028                                 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0)
1029                                 glEnable(GL_BLEND)
1030                                 glEnable(GL_CULL_FACE)
1031                                 glColor4f(0,0,0,0.12)
1032                                 glBegin(GL_QUADS)
1033                                 glVertex3f(-size[0],  size[1], 0.1)
1034                                 glVertex3f(-size[0], -size[1], 0.1)
1035                                 glVertex3f( size[0], -size[1], 0.1)
1036                                 glVertex3f( size[0],  size[1], 0.1)
1037                                 glEnd()
1038                                 glDisable(GL_CULL_FACE)
1039                                 glPopMatrix()
1040
1041                         #Draw the outline of the selected object, on top of everything else except the GUI.
1042                         if self._selectedObj is not None and self._selectedObj._loadAnim is None:
1043                                 glDisable(GL_DEPTH_TEST)
1044                                 glEnable(GL_CULL_FACE)
1045                                 glEnable(GL_STENCIL_TEST)
1046                                 glDisable(GL_BLEND)
1047                                 glStencilFunc(GL_EQUAL, 0, 255)
1048
1049                                 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
1050                                 glLineWidth(2)
1051                                 glColor4f(1,1,1,0.5)
1052                                 self._renderObject(self._selectedObj)
1053                                 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
1054
1055                                 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
1056                                 glDisable(GL_STENCIL_TEST)
1057                                 glDisable(GL_CULL_FACE)
1058                                 glEnable(GL_DEPTH_TEST)
1059
1060                         if self._selectedObj is not None:
1061                                 glPushMatrix()
1062                                 pos = self.getObjectCenterPos()
1063                                 glTranslate(pos[0], pos[1], pos[2])
1064                                 self.tool.OnDraw()
1065                                 glPopMatrix()
1066                 if self.viewMode == 'overhang' and not opengl.hasShaderSupport():
1067                         glDisable(GL_DEPTH_TEST)
1068                         glPushMatrix()
1069                         glLoadIdentity()
1070                         glTranslate(0,-4,-10)
1071                         glColor4ub(60,60,60,255)
1072                         opengl.glDrawStringCenter('Overhang view not working due to lack of OpenGL shaders support.')
1073                         glPopMatrix()
1074
1075         def _renderObject(self, obj, brightness = False, addSink = True):
1076                 glPushMatrix()
1077                 if addSink:
1078                         glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2 - profile.getProfileSettingFloat('object_sink'))
1079                 else:
1080                         glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
1081
1082                 if self.tempMatrix is not None and obj == self._selectedObj:
1083                         tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
1084                         glMultMatrixf(tempMatrix)
1085
1086                 offset = obj.getDrawOffset()
1087                 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
1088
1089                 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
1090                 glMultMatrixf(tempMatrix)
1091
1092                 n = 0
1093                 for m in obj._meshList:
1094                         if m.vbo is None:
1095                                 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
1096                         if brightness:
1097                                 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
1098                                 n += 1
1099                         m.vbo.render()
1100                 glPopMatrix()
1101
1102         def _drawMachine(self):
1103                 glEnable(GL_CULL_FACE)
1104                 glEnable(GL_BLEND)
1105
1106                 if profile.getPreference('machine_type') == 'ultimaker':
1107                         glColor4f(1,1,1,0.5)
1108                         self._objectShader.bind()
1109                         self._renderObject(self._platformMesh, False, False)
1110                         self._objectShader.unbind()
1111
1112                 size = [profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')]
1113                 v0 = [ size[0] / 2, size[1] / 2, size[2]]
1114                 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
1115                 v2 = [-size[0] / 2, size[1] / 2, size[2]]
1116                 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
1117                 v4 = [ size[0] / 2, size[1] / 2, 0]
1118                 v5 = [ size[0] / 2,-size[1] / 2, 0]
1119                 v6 = [-size[0] / 2, size[1] / 2, 0]
1120                 v7 = [-size[0] / 2,-size[1] / 2, 0]
1121
1122                 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
1123                 glEnableClientState(GL_VERTEX_ARRAY)
1124                 glVertexPointer(3, GL_FLOAT, 3*4, vList)
1125
1126                 glColor4ub(5, 171, 231, 64)
1127                 glDrawArrays(GL_QUADS, 0, 4)
1128                 glColor4ub(5, 171, 231, 96)
1129                 glDrawArrays(GL_QUADS, 4, 8)
1130                 glColor4ub(5, 171, 231, 128)
1131                 glDrawArrays(GL_QUADS, 12, 8)
1132
1133                 sx = self._machineSize[0]
1134                 sy = self._machineSize[1]
1135                 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
1136                         for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
1137                                 x1 = x * 10
1138                                 x2 = x1 + 10
1139                                 y1 = y * 10
1140                                 y2 = y1 + 10
1141                                 x1 = max(min(x1, sx/2), -sx/2)
1142                                 y1 = max(min(y1, sy/2), -sy/2)
1143                                 x2 = max(min(x2, sx/2), -sx/2)
1144                                 y2 = max(min(y2, sy/2), -sy/2)
1145                                 if (x & 1) == (y & 1):
1146                                         glColor4ub(5, 171, 231, 127)
1147                                 else:
1148                                         glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
1149                                 glBegin(GL_QUADS)
1150                                 glVertex3f(x1, y1, -0.02)
1151                                 glVertex3f(x2, y1, -0.02)
1152                                 glVertex3f(x2, y2, -0.02)
1153                                 glVertex3f(x1, y2, -0.02)
1154                                 glEnd()
1155
1156                 glDisableClientState(GL_VERTEX_ARRAY)
1157                 glDisable(GL_BLEND)
1158                 glDisable(GL_CULL_FACE)
1159
1160         def _generateGCodeVBOs(self, layer):
1161                 ret = []
1162                 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1163                         pointList = numpy.zeros((0,3), numpy.float32)
1164                         for path in layer:
1165                                 if path['type'] == 'extrude' and path['pathType'] == extrudeType:
1166                                         a = path['points']
1167                                         a = numpy.concatenate((a[:-1], a[1:]), 1)
1168                                         a = a.reshape((len(a) * 2, 3))
1169                                         pointList = numpy.concatenate((pointList, a))
1170                         ret.append(opengl.GLVBO(pointList))
1171                 return ret
1172
1173         def _generateGCodeVBOs2(self, layer):
1174                 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
1175                 filamentArea = math.pi * filamentRadius * filamentRadius
1176
1177                 ret = []
1178                 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1179                         pointList = numpy.zeros((0,3), numpy.float32)
1180                         for path in layer:
1181                                 if path['type'] == 'extrude' and path['pathType'] == extrudeType:
1182                                         a = path['points']
1183                                         if extrudeType == 'FILL':
1184                                                 a[:,2] += 0.01
1185
1186                                         normal = a[1:] - a[:-1]
1187                                         lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
1188                                         normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
1189                                         normal[:,2] /= lens
1190
1191                                         ePerDist = path['extrusion'][1:] / lens
1192                                         lineWidth = ePerDist * (filamentArea / path['layerThickness'] / 2)
1193
1194                                         normal[:,0] *= lineWidth
1195                                         normal[:,1] *= lineWidth
1196
1197                                         b = numpy.zeros((len(a)-1, 0), numpy.float32)
1198                                         b = numpy.concatenate((b, a[1:] + normal), 1)
1199                                         b = numpy.concatenate((b, a[1:] - normal), 1)
1200                                         b = numpy.concatenate((b, a[:-1] - normal), 1)
1201                                         b = numpy.concatenate((b, a[:-1] + normal), 1)
1202                                         #b = numpy.concatenate((b, a[:-1]), 1)
1203                                         #b = numpy.concatenate((b, a[:-1]), 1)
1204                                         b = b.reshape((len(b) * 4, 3))
1205
1206                                         if len(a) > 2:
1207                                                 normal2 = normal[:-1] + normal[1:]
1208                                                 lens2 = numpy.sqrt(normal2[:,0]**2 + normal2[:,1]**2)
1209                                                 normal2[:,0] /= lens2
1210                                                 normal2[:,1] /= lens2
1211                                                 normal2[:,0] *= lineWidth[:-1]
1212                                                 normal2[:,1] *= lineWidth[:-1]
1213
1214                                                 c = numpy.zeros((len(a)-2, 0), numpy.float32)
1215                                                 c = numpy.concatenate((c, a[1:-1]), 1)
1216                                                 c = numpy.concatenate((c, a[1:-1]+normal[1:]), 1)
1217                                                 c = numpy.concatenate((c, a[1:-1]+normal2), 1)
1218                                                 c = numpy.concatenate((c, a[1:-1]+normal[:-1]), 1)
1219
1220                                                 c = numpy.concatenate((c, a[1:-1]), 1)
1221                                                 c = numpy.concatenate((c, a[1:-1]-normal[1:]), 1)
1222                                                 c = numpy.concatenate((c, a[1:-1]-normal2), 1)
1223                                                 c = numpy.concatenate((c, a[1:-1]-normal[:-1]), 1)
1224
1225                                                 c = c.reshape((len(c) * 8, 3))
1226
1227                                                 pointList = numpy.concatenate((pointList, b, c))
1228                                         else:
1229                                                 pointList = numpy.concatenate((pointList, b))
1230                         ret.append(opengl.GLVBO(pointList))
1231
1232                 pointList = numpy.zeros((0,3), numpy.float32)
1233                 for path in layer:
1234                         if path['type'] == 'move':
1235                                 a = path['points'] + numpy.array([0,0,0.01], numpy.float32)
1236                                 a = numpy.concatenate((a[:-1], a[1:]), 1)
1237                                 a = a.reshape((len(a) * 2, 3))
1238                                 pointList = numpy.concatenate((pointList, a))
1239                         if path['type'] == 'retract':
1240                                 a = path['points'] + numpy.array([0,0,0.01], numpy.float32)
1241                                 a = numpy.concatenate((a[:-1], a[1:] + numpy.array([0,0,1], numpy.float32)), 1)
1242                                 a = a.reshape((len(a) * 2, 3))
1243                                 pointList = numpy.concatenate((pointList, a))
1244                 ret.append(opengl.GLVBO(pointList))
1245
1246                 return ret
1247
1248         def getObjectCenterPos(self):
1249                 if self._selectedObj is None:
1250                         return [0.0, 0.0, 0.0]
1251                 pos = self._selectedObj.getPosition()
1252                 size = self._selectedObj.getSize()
1253                 return [pos[0], pos[1], size[2]/2 - profile.getProfileSettingFloat('object_sink')]
1254
1255         def getObjectBoundaryCircle(self):
1256                 if self._selectedObj is None:
1257                         return 0.0
1258                 return self._selectedObj.getBoundaryCircle()
1259
1260         def getObjectSize(self):
1261                 if self._selectedObj is None:
1262                         return [0.0, 0.0, 0.0]
1263                 return self._selectedObj.getSize()
1264
1265         def getObjectMatrix(self):
1266                 if self._selectedObj is None:
1267                         return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1268                 return self._selectedObj.getMatrix()
1269
1270 class shaderEditor(wx.Dialog):
1271         def __init__(self, parent, callback, v, f):
1272                 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1273                 self._callback = callback
1274                 s = wx.BoxSizer(wx.VERTICAL)
1275                 self.SetSizer(s)
1276                 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1277                 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1278                 s.Add(self._vertex, 1, flag=wx.EXPAND)
1279                 s.Add(self._fragment, 1, flag=wx.EXPAND)
1280
1281                 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1282                 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1283
1284                 self.SetPosition(self.GetParent().GetPosition())
1285                 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1286                 self.Show()
1287
1288         def OnText(self, e):
1289                 self._callback(self._vertex.GetValue(), self._fragment.GetValue())