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