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