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