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