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