chiark / gitweb /
Added keyboard control for the 3D window to look around with cursor keys.
[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 self.viewMode == 'gcode':
665                         if keyCode == wx.WXK_UP:
666                                 self.layerSelect.setValue(self.layerSelect.getValue() + 1)
667                                 self.QueueRefresh()
668                         elif keyCode == wx.WXK_DOWN:
669                                 self.layerSelect.setValue(self.layerSelect.getValue() - 1)
670                                 self.QueueRefresh()
671                         elif keyCode == wx.WXK_PAGEUP:
672                                 self.layerSelect.setValue(self.layerSelect.getValue() + 10)
673                                 self.QueueRefresh()
674                         elif keyCode == wx.WXK_PAGEDOWN:
675                                 self.layerSelect.setValue(self.layerSelect.getValue() - 10)
676                                 self.QueueRefresh()
677                 else:
678                         if keyCode == wx.WXK_UP:
679                                 if wx.GetKeyState(wx.WXK_SHIFT):
680                                         self._zoom /= 1.2
681                                         if self._zoom < 1:
682                                                 self._zoom = 1
683                                 else:
684                                         self._pitch -= 15
685                                 self.QueueRefresh()
686                         elif keyCode == wx.WXK_DOWN:
687                                 if wx.GetKeyState(wx.WXK_SHIFT):
688                                         self._zoom *= 1.2
689                                         if self._zoom > numpy.max(self._machineSize) * 3:
690                                                 self._zoom = numpy.max(self._machineSize) * 3
691                                 else:
692                                         self._pitch += 15
693                                 self.QueueRefresh()
694                         elif keyCode == wx.WXK_LEFT:
695                                 self._yaw -= 15
696                                 self.QueueRefresh()
697                         elif keyCode == wx.WXK_RIGHT:
698                                 self._yaw += 15
699                                 self.QueueRefresh()
700                         elif keyCode == wx.WXK_NUMPAD_ADD or keyCode == wx.WXK_ADD or keyCode == ord('+') or keyCode == ord('='):
701                                 self._zoom /= 1.2
702                                 if self._zoom < 1:
703                                         self._zoom = 1
704                                 self.QueueRefresh()
705                         elif keyCode == wx.WXK_NUMPAD_SUBTRACT or keyCode == wx.WXK_SUBTRACT or keyCode == ord('-'):
706                                 self._zoom *= 1.2
707                                 if self._zoom > numpy.max(self._machineSize) * 3:
708                                         self._zoom = numpy.max(self._machineSize) * 3
709                                 self.QueueRefresh()
710                         elif keyCode == wx.WXK_HOME:
711                                 self._yaw = 30
712                                 self._pitch = 60
713                                 self.QueueRefresh()
714                         elif keyCode == wx.WXK_PAGEUP:
715                                 self._yaw = 0
716                                 self._pitch = 0
717                                 self.QueueRefresh()
718                         elif keyCode == wx.WXK_PAGEDOWN:
719                                 self._yaw = 0
720                                 self._pitch = 90
721                                 self.QueueRefresh()
722                         elif keyCode == wx.WXK_END:
723                                 self._yaw = 90
724                                 self._pitch = 90
725                                 self.QueueRefresh()
726
727                 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
728                         shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
729                 if keyCode == wx.WXK_F4 and wx.GetKeyState(wx.WXK_SHIFT):
730                         from collections import defaultdict
731                         from gc import get_objects
732                         self._beforeLeakTest = defaultdict(int)
733                         for i in get_objects():
734                                 self._beforeLeakTest[type(i)] += 1
735                 if keyCode == wx.WXK_F5 and wx.GetKeyState(wx.WXK_SHIFT):
736                         from collections import defaultdict
737                         from gc import get_objects
738                         self._afterLeakTest = defaultdict(int)
739                         for i in get_objects():
740                                 self._afterLeakTest[type(i)] += 1
741                         for k in self._afterLeakTest:
742                                 if self._afterLeakTest[k]-self._beforeLeakTest[k]:
743                                         print k, self._afterLeakTest[k], self._beforeLeakTest[k], self._afterLeakTest[k] - self._beforeLeakTest[k]
744
745         def ShaderUpdate(self, v, f):
746                 s = opengl.GLShader(v, f)
747                 if s.isValid():
748                         self._objectLoadShader.release()
749                         self._objectLoadShader = s
750                         for obj in self._scene.objects():
751                                 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
752                         self.QueueRefresh()
753
754         def OnMouseDown(self,e):
755                 self._mouseX = e.GetX()
756                 self._mouseY = e.GetY()
757                 self._mouseClick3DPos = self._mouse3Dpos
758                 self._mouseClickFocus = self._focusObj
759                 if e.ButtonDClick():
760                         self._mouseState = 'doubleClick'
761                 else:
762                         self._mouseState = 'dragOrClick'
763                 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
764                 p0 -= self.getObjectCenterPos() - self._viewTarget
765                 p1 -= self.getObjectCenterPos() - self._viewTarget
766                 if self.tool.OnDragStart(p0, p1):
767                         self._mouseState = 'tool'
768                 if self._mouseState == 'dragOrClick':
769                         if e.GetButton() == 1:
770                                 if self._focusObj is not None:
771                                         self._selectObject(self._focusObj, False)
772                                         self.QueueRefresh()
773
774         def OnMouseUp(self, e):
775                 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
776                         return
777                 if self._mouseState == 'dragOrClick':
778                         if e.GetButton() == 1:
779                                 self._selectObject(self._focusObj)
780                         if e.GetButton() == 3:
781                                         menu = wx.Menu()
782                                         if self._focusObj is not None:
783                                                 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, _("Delete object")))
784                                                 self.Bind(wx.EVT_MENU, self.OnCenter, menu.Append(-1, _("Center on platform")))
785                                                 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, _("Multiply object")))
786                                                 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, _("Split object into parts")))
787                                         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:
788                                                 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, _("Dual extrusion merge")))
789                                         if len(self._scene.objects()) > 0:
790                                                 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, _("Delete all objects")))
791                                         if menu.MenuItemCount > 0:
792                                                 self.PopupMenu(menu)
793                                         menu.Destroy()
794                 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
795                         self._scene.pushFree()
796                         self.sceneUpdated()
797                 elif self._mouseState == 'tool':
798                         if self.tempMatrix is not None and self._selectedObj is not None:
799                                 self._selectedObj.applyMatrix(self.tempMatrix)
800                                 self._scene.pushFree()
801                                 self._selectObject(self._selectedObj)
802                         self.tempMatrix = None
803                         self.tool.OnDragEnd()
804                         self.sceneUpdated()
805                 self._mouseState = None
806
807         def OnMouseMotion(self,e):
808                 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
809                 p0 -= self.getObjectCenterPos() - self._viewTarget
810                 p1 -= self.getObjectCenterPos() - self._viewTarget
811
812                 if e.Dragging() and self._mouseState is not None:
813                         if self._mouseState == 'tool':
814                                 self.tool.OnDrag(p0, p1)
815                         elif not e.LeftIsDown() and e.RightIsDown():
816                                 self._mouseState = 'drag'
817                                 if wx.GetKeyState(wx.WXK_SHIFT):
818                                         a = math.cos(math.radians(self._yaw)) / 3.0
819                                         b = math.sin(math.radians(self._yaw)) / 3.0
820                                         self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
821                                         self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
822                                         self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
823                                         self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
824                                 else:
825                                         self._yaw += e.GetX() - self._mouseX
826                                         self._pitch -= e.GetY() - self._mouseY
827                                 if self._pitch > 170:
828                                         self._pitch = 170
829                                 if self._pitch < 10:
830                                         self._pitch = 10
831                         elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
832                                 self._mouseState = 'drag'
833                                 self._zoom += e.GetY() - self._mouseY
834                                 if self._zoom < 1:
835                                         self._zoom = 1
836                                 if self._zoom > numpy.max(self._machineSize) * 3:
837                                         self._zoom = numpy.max(self._machineSize) * 3
838                         elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus:
839                                 self._mouseState = 'dragObject'
840                                 z = max(0, self._mouseClick3DPos[2])
841                                 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
842                                 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
843                                 p0[2] -= z
844                                 p1[2] -= z
845                                 p2[2] -= z
846                                 p3[2] -= z
847                                 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
848                                 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
849                                 diff = cursorZ1 - cursorZ0
850                                 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
851                 if not e.Dragging() or self._mouseState != 'tool':
852                         self.tool.OnMouseMove(p0, p1)
853
854                 self._mouseX = e.GetX()
855                 self._mouseY = e.GetY()
856
857         def OnMouseWheel(self, e):
858                 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
859                 delta = max(min(delta,4),-4)
860                 self._zoom *= 1.0 - delta / 10.0
861                 if self._zoom < 1.0:
862                         self._zoom = 1.0
863                 if self._zoom > numpy.max(self._machineSize) * 3:
864                         self._zoom = numpy.max(self._machineSize) * 3
865                 self.Refresh()
866
867         def OnMouseLeave(self, e):
868                 #self._mouseX = -1
869                 pass
870
871         def getMouseRay(self, x, y):
872                 if self._viewport is None:
873                         return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
874                 p0 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
875                 p1 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
876                 p0 -= self._viewTarget
877                 p1 -= self._viewTarget
878                 return p0, p1
879
880         def _init3DView(self):
881                 # set viewing projection
882                 size = self.GetSize()
883                 glViewport(0, 0, size.GetWidth(), size.GetHeight())
884                 glLoadIdentity()
885
886                 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
887
888                 glDisable(GL_RESCALE_NORMAL)
889                 glDisable(GL_LIGHTING)
890                 glDisable(GL_LIGHT0)
891                 glEnable(GL_DEPTH_TEST)
892                 glDisable(GL_CULL_FACE)
893                 glDisable(GL_BLEND)
894                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
895
896                 glClearColor(0.8, 0.8, 0.8, 1.0)
897                 glClearStencil(0)
898                 glClearDepth(1.0)
899
900                 glMatrixMode(GL_PROJECTION)
901                 glLoadIdentity()
902                 aspect = float(size.GetWidth()) / float(size.GetHeight())
903                 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
904
905                 glMatrixMode(GL_MODELVIEW)
906                 glLoadIdentity()
907                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
908
909         def OnPaint(self,e):
910                 connectionEntry = self._printerConnectionManager.getAvailableConnection()
911                 if machineCom.machineIsConnected():
912                         self.printButton._imageID = 6
913                         self.printButton._tooltip = _("Print")
914                 elif len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionEntry is None or connectionEntry.priority < 0):
915                         self.printButton._imageID = 2
916                         self.printButton._tooltip = _("Toolpath to SD")
917                 elif connectionEntry is not None:
918                         self.printButton._imageID = connectionEntry.icon
919                         self.printButton._tooltip = _("Print with %s") % (connectionEntry.name)
920                 else:
921                         self.printButton._imageID = 3
922                         self.printButton._tooltip = _("Save toolpath")
923
924                 if self._animView is not None:
925                         self._viewTarget = self._animView.getPosition()
926                         if self._animView.isDone():
927                                 self._animView = None
928                 if self._animZoom is not None:
929                         self._zoom = self._animZoom.getPosition()
930                         if self._animZoom.isDone():
931                                 self._animZoom = None
932                 if self.viewMode == 'gcode' and self._gcode is not None:
933                         try:
934                                 self._viewTarget[2] = self._gcode.layerList[self.layerSelect.getValue()][-1]['points'][0][2]
935                         except:
936                                 pass
937                 if self._objectShader is None:
938                         if opengl.hasShaderSupport():
939                                 self._objectShader = opengl.GLShader("""
940 varying float light_amount;
941
942 void main(void)
943 {
944     gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
945     gl_FrontColor = gl_Color;
946
947         light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
948         light_amount += 0.2;
949 }
950                                 ""","""
951 varying float light_amount;
952
953 void main(void)
954 {
955         gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
956 }
957                                 """)
958                                 self._objectOverhangShader = opengl.GLShader("""
959 uniform float cosAngle;
960 uniform mat3 rotMatrix;
961 varying float light_amount;
962
963 void main(void)
964 {
965     gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
966     gl_FrontColor = gl_Color;
967
968         light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
969         light_amount += 0.2;
970         if (normalize(rotMatrix * gl_Normal).z < -cosAngle)
971         {
972                 light_amount = -10.0;
973         }
974 }
975                                 ""","""
976 varying float light_amount;
977
978 void main(void)
979 {
980         if (light_amount == -10.0)
981         {
982                 gl_FragColor = vec4(1.0, 0.0, 0.0, gl_Color[3]);
983         }else{
984                 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
985         }
986 }
987                                 """)
988                                 self._objectLoadShader = opengl.GLShader("""
989 uniform float intensity;
990 uniform float scale;
991 varying float light_amount;
992
993 void main(void)
994 {
995         vec4 tmp = gl_Vertex;
996     tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
997     tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
998     gl_Position = gl_ModelViewProjectionMatrix * tmp;
999     gl_FrontColor = gl_Color;
1000
1001         light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
1002         light_amount += 0.2;
1003 }
1004                         ""","""
1005 uniform float intensity;
1006 varying float light_amount;
1007
1008 void main(void)
1009 {
1010         gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
1011 }
1012                                 """)
1013                         if self._objectShader is None or not self._objectShader.isValid():
1014                                 self._objectShader = opengl.GLFakeShader()
1015                                 self._objectOverhangShader = opengl.GLFakeShader()
1016                                 self._objectLoadShader = None
1017                 self._init3DView()
1018                 glTranslate(0,0,-self._zoom)
1019                 glRotate(-self._pitch, 1,0,0)
1020                 glRotate(self._yaw, 0,0,1)
1021                 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
1022
1023                 self._viewport = glGetIntegerv(GL_VIEWPORT)
1024                 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
1025                 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
1026
1027                 glClearColor(1,1,1,1)
1028                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
1029
1030                 if self.viewMode != 'gcode':
1031                         for n in xrange(0, len(self._scene.objects())):
1032                                 obj = self._scene.objects()[n]
1033                                 glColor4ub((n >> 16) & 0xFF, (n >> 8) & 0xFF, (n >> 0) & 0xFF, 0xFF)
1034                                 self._renderObject(obj)
1035
1036                 if self._mouseX > -1:
1037                         glFlush()
1038                         n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0] >> 8
1039                         if n < len(self._scene.objects()):
1040                                 self._focusObj = self._scene.objects()[n]
1041                         else:
1042                                 self._focusObj = None
1043                         f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
1044                         #self.GetTopLevelParent().SetTitle(hex(n) + " " + str(f))
1045                         self._mouse3Dpos = opengl.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
1046                         self._mouse3Dpos -= self._viewTarget
1047
1048                 self._init3DView()
1049                 glTranslate(0,0,-self._zoom)
1050                 glRotate(-self._pitch, 1,0,0)
1051                 glRotate(self._yaw, 0,0,1)
1052                 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
1053
1054                 if self.viewMode == 'gcode':
1055                         if self._gcode is not None and self._gcode.layerList is None:
1056                                 self._gcodeLoadThread = threading.Thread(target=self._loadGCode)
1057                                 self._gcodeLoadThread.daemon = True
1058                                 self._gcodeLoadThread.start()
1059                         if self._gcode is not None and self._gcode.layerList is not None:
1060                                 glPushMatrix()
1061                                 if profile.getMachineSetting('machine_center_is_zero') != 'True':
1062                                         glTranslate(-self._machineSize[0] / 2, -self._machineSize[1] / 2, 0)
1063                                 t = time.time()
1064                                 drawUpTill = min(len(self._gcode.layerList), self.layerSelect.getValue() + 1)
1065                                 for n in xrange(0, drawUpTill):
1066                                         c = 1.0 - float(drawUpTill - n) / 15
1067                                         c = max(0.3, c)
1068                                         if len(self._gcodeVBOs) < n + 1:
1069                                                 self._gcodeVBOs.append(self._generateGCodeVBOs(self._gcode.layerList[n]))
1070                                                 if time.time() - t > 0.5:
1071                                                         self.QueueRefresh()
1072                                                         break
1073                                         #['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']
1074                                         if n == drawUpTill - 1:
1075                                                 if len(self._gcodeVBOs[n]) < 9:
1076                                                         self._gcodeVBOs[n] += self._generateGCodeVBOs2(self._gcode.layerList[n])
1077                                                 glColor3f(c, 0, 0)
1078                                                 self._gcodeVBOs[n][8].render(GL_QUADS)
1079                                                 glColor3f(c/2, 0, c)
1080                                                 self._gcodeVBOs[n][9].render(GL_QUADS)
1081                                                 glColor3f(0, c, c/2)
1082                                                 self._gcodeVBOs[n][10].render(GL_QUADS)
1083                                                 glColor3f(c, 0, 0)
1084                                                 self._gcodeVBOs[n][11].render(GL_QUADS)
1085
1086                                                 glColor3f(0, c, 0)
1087                                                 self._gcodeVBOs[n][12].render(GL_QUADS)
1088                                                 glColor3f(c/2, c/2, 0.0)
1089                                                 self._gcodeVBOs[n][13].render(GL_QUADS)
1090                                                 glColor3f(0, c, c)
1091                                                 self._gcodeVBOs[n][14].render(GL_QUADS)
1092                                                 self._gcodeVBOs[n][15].render(GL_QUADS)
1093                                                 glColor3f(0, 0, c)
1094                                                 self._gcodeVBOs[n][16].render(GL_LINES)
1095                                         else:
1096                                                 glColor3f(c, 0, 0)
1097                                                 self._gcodeVBOs[n][0].render(GL_LINES)
1098                                                 glColor3f(c/2, 0, c)
1099                                                 self._gcodeVBOs[n][1].render(GL_LINES)
1100                                                 glColor3f(0, c, c/2)
1101                                                 self._gcodeVBOs[n][2].render(GL_LINES)
1102                                                 glColor3f(c, 0, 0)
1103                                                 self._gcodeVBOs[n][3].render(GL_LINES)
1104
1105                                                 glColor3f(0, c, 0)
1106                                                 self._gcodeVBOs[n][4].render(GL_LINES)
1107                                                 glColor3f(c/2, c/2, 0.0)
1108                                                 self._gcodeVBOs[n][5].render(GL_LINES)
1109                                                 glColor3f(0, c, c)
1110                                                 self._gcodeVBOs[n][6].render(GL_LINES)
1111                                                 self._gcodeVBOs[n][7].render(GL_LINES)
1112                                 glPopMatrix()
1113                 else:
1114                         glStencilFunc(GL_ALWAYS, 1, 1)
1115                         glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1116
1117                         if self.viewMode == 'overhang':
1118                                 self._objectOverhangShader.bind()
1119                                 self._objectOverhangShader.setUniform('cosAngle', math.cos(math.radians(90 - 60)))
1120                         else:
1121                                 self._objectShader.bind()
1122                         for obj in self._scene.objects():
1123                                 if obj._loadAnim is not None:
1124                                         if obj._loadAnim.isDone():
1125                                                 obj._loadAnim = None
1126                                         else:
1127                                                 continue
1128                                 brightness = 1.0
1129                                 if self._focusObj == obj:
1130                                         brightness = 1.2
1131                                 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
1132                                         brightness = 0.8
1133
1134                                 if self._selectedObj == obj or self._selectedObj is None:
1135                                         #If we want transparent, then first render a solid black model to remove the printer size lines.
1136                                         if self.viewMode == 'transparent':
1137                                                 glColor4f(0, 0, 0, 0)
1138                                                 self._renderObject(obj)
1139                                                 glEnable(GL_BLEND)
1140                                                 glBlendFunc(GL_ONE, GL_ONE)
1141                                                 glDisable(GL_DEPTH_TEST)
1142                                                 brightness *= 0.5
1143                                         if self.viewMode == 'xray':
1144                                                 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
1145                                         glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1146                                         glEnable(GL_STENCIL_TEST)
1147
1148                                 if self.viewMode == 'overhang':
1149                                         if self._selectedObj == obj and self.tempMatrix is not None:
1150                                                 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix() * self.tempMatrix)
1151                                         else:
1152                                                 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix())
1153
1154                                 if not self._scene.checkPlatform(obj):
1155                                         glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
1156                                         self._renderObject(obj)
1157                                 else:
1158                                         self._renderObject(obj, brightness)
1159                                 glDisable(GL_STENCIL_TEST)
1160                                 glDisable(GL_BLEND)
1161                                 glEnable(GL_DEPTH_TEST)
1162                                 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
1163
1164                         if self.viewMode == 'xray':
1165                                 glPushMatrix()
1166                                 glLoadIdentity()
1167                                 glEnable(GL_STENCIL_TEST)
1168                                 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
1169                                 glDisable(GL_DEPTH_TEST)
1170                                 for i in xrange(2, 15, 2):
1171                                         glStencilFunc(GL_EQUAL, i, 0xFF)
1172                                         glColor(float(i)/10, float(i)/10, float(i)/5)
1173                                         glBegin(GL_QUADS)
1174                                         glVertex3f(-1000,-1000,-10)
1175                                         glVertex3f( 1000,-1000,-10)
1176                                         glVertex3f( 1000, 1000,-10)
1177                                         glVertex3f(-1000, 1000,-10)
1178                                         glEnd()
1179                                 for i in xrange(1, 15, 2):
1180                                         glStencilFunc(GL_EQUAL, i, 0xFF)
1181                                         glColor(float(i)/10, 0, 0)
1182                                         glBegin(GL_QUADS)
1183                                         glVertex3f(-1000,-1000,-10)
1184                                         glVertex3f( 1000,-1000,-10)
1185                                         glVertex3f( 1000, 1000,-10)
1186                                         glVertex3f(-1000, 1000,-10)
1187                                         glEnd()
1188                                 glPopMatrix()
1189                                 glDisable(GL_STENCIL_TEST)
1190                                 glEnable(GL_DEPTH_TEST)
1191
1192                         self._objectShader.unbind()
1193
1194                         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1195                         glEnable(GL_BLEND)
1196                         if self._objectLoadShader is not None:
1197                                 self._objectLoadShader.bind()
1198                                 glColor4f(0.2, 0.6, 1.0, 1.0)
1199                                 for obj in self._scene.objects():
1200                                         if obj._loadAnim is None:
1201                                                 continue
1202                                         self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
1203                                         self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
1204                                         self._renderObject(obj)
1205                                 self._objectLoadShader.unbind()
1206                                 glDisable(GL_BLEND)
1207
1208                 self._drawMachine()
1209
1210                 if self._usbPrintMonitor.getState() == 'PRINTING' and self._usbPrintMonitor.getID() == self._slicer.getID():
1211                         glEnable(GL_BLEND)
1212                         z = self._usbPrintMonitor.getZ()
1213                         size = self._machineSize
1214                         glColor4ub(255,255,0,128)
1215                         glBegin(GL_QUADS)
1216                         glVertex3f(-size[0]/2,-size[1]/2, z)
1217                         glVertex3f( size[0]/2,-size[1]/2, z)
1218                         glVertex3f( size[0]/2, size[1]/2, z)
1219                         glVertex3f(-size[0]/2, size[1]/2, z)
1220                         glEnd()
1221
1222                 if self.viewMode == 'gcode':
1223                         if self._gcodeLoadThread is not None and self._gcodeLoadThread.isAlive():
1224                                 glDisable(GL_DEPTH_TEST)
1225                                 glPushMatrix()
1226                                 glLoadIdentity()
1227                                 glTranslate(0,-4,-10)
1228                                 glColor4ub(60,60,60,255)
1229                                 opengl.glDrawStringCenter(_("Loading toolpath for visualization..."))
1230                                 glPopMatrix()
1231                 else:
1232                         #Draw the object box-shadow, so you can see where it will collide with other objects.
1233                         if self._selectedObj is not None:
1234                                 glEnable(GL_BLEND)
1235                                 glEnable(GL_CULL_FACE)
1236                                 glColor4f(0,0,0,0.16)
1237                                 glDepthMask(False)
1238                                 for obj in self._scene.objects():
1239                                         glPushMatrix()
1240                                         glTranslatef(obj.getPosition()[0], obj.getPosition()[1], 0)
1241                                         glBegin(GL_TRIANGLE_FAN)
1242                                         for p in obj._boundaryHull[::-1]:
1243                                                 glVertex3f(p[0], p[1], 0)
1244                                         glEnd()
1245                                         glPopMatrix()
1246                                 glPushMatrix()
1247                                 glColor4f(0,0,0,0.06)
1248                                 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0)
1249                                 glBegin(GL_TRIANGLE_FAN)
1250                                 for p in self._selectedObj._printAreaHull[::-1]:
1251                                         glVertex3f(p[0], p[1], 0)
1252                                 glEnd()
1253                                 glBegin(GL_TRIANGLE_FAN)
1254                                 for p in self._selectedObj._headAreaMinHull[::-1]:
1255                                         glVertex3f(p[0], p[1], 0)
1256                                 glEnd()
1257                                 glDepthMask(True)
1258                                 glDisable(GL_CULL_FACE)
1259                                 glPopMatrix()
1260
1261                         #Draw the outline of the selected object, on top of everything else except the GUI.
1262                         if self._selectedObj is not None and self._selectedObj._loadAnim is None:
1263                                 glDisable(GL_DEPTH_TEST)
1264                                 glEnable(GL_CULL_FACE)
1265                                 glEnable(GL_STENCIL_TEST)
1266                                 glDisable(GL_BLEND)
1267                                 glStencilFunc(GL_EQUAL, 0, 255)
1268
1269                                 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
1270                                 glLineWidth(2)
1271                                 glColor4f(1,1,1,0.5)
1272                                 self._renderObject(self._selectedObj)
1273                                 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
1274
1275                                 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
1276                                 glDisable(GL_STENCIL_TEST)
1277                                 glDisable(GL_CULL_FACE)
1278                                 glEnable(GL_DEPTH_TEST)
1279
1280                         if self._selectedObj is not None:
1281                                 glPushMatrix()
1282                                 pos = self.getObjectCenterPos()
1283                                 glTranslate(pos[0], pos[1], pos[2])
1284                                 self.tool.OnDraw()
1285                                 glPopMatrix()
1286                 if self.viewMode == 'overhang' and not opengl.hasShaderSupport():
1287                         glDisable(GL_DEPTH_TEST)
1288                         glPushMatrix()
1289                         glLoadIdentity()
1290                         glTranslate(0,-4,-10)
1291                         glColor4ub(60,60,60,255)
1292                         opengl.glDrawStringCenter(_("Overhang view not working due to lack of OpenGL shaders support."))
1293                         glPopMatrix()
1294
1295         def _renderObject(self, obj, brightness = False, addSink = True):
1296                 glPushMatrix()
1297                 if addSink:
1298                         glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2 - profile.getProfileSettingFloat('object_sink'))
1299                 else:
1300                         glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
1301
1302                 if self.tempMatrix is not None and obj == self._selectedObj:
1303                         tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
1304                         glMultMatrixf(tempMatrix)
1305
1306                 offset = obj.getDrawOffset()
1307                 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
1308
1309                 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
1310                 glMultMatrixf(tempMatrix)
1311
1312                 n = 0
1313                 for m in obj._meshList:
1314                         if m.vbo is None:
1315                                 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
1316                         if brightness:
1317                                 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
1318                                 n += 1
1319                         m.vbo.render()
1320                 glPopMatrix()
1321
1322         def _drawMachine(self):
1323                 glEnable(GL_CULL_FACE)
1324                 glEnable(GL_BLEND)
1325
1326                 size = [profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')]
1327
1328                 machine = profile.getMachineSetting('machine_type')
1329                 if machine.startswith('ultimaker'):
1330                         if machine not in self._platformMesh:
1331                                 meshes = meshLoader.loadMeshes(resources.getPathForMesh(machine + '_platform.stl'))
1332                                 if len(meshes) > 0:
1333                                         self._platformMesh[machine] = meshes[0]
1334                                 else:
1335                                         self._platformMesh[machine] = None
1336                                 if machine == 'ultimaker2':
1337                                         self._platformMesh[machine]._drawOffset = numpy.array([0,-37,145], numpy.float32)
1338                                 else:
1339                                         self._platformMesh[machine]._drawOffset = numpy.array([0,0,2.5], numpy.float32)
1340                         glColor4f(1,1,1,0.5)
1341                         self._objectShader.bind()
1342                         self._renderObject(self._platformMesh[machine], False, False)
1343                         self._objectShader.unbind()
1344
1345                         #For the Ultimaker 2 render the texture on the back plate to show the Ultimaker2 text.
1346                         if machine == 'ultimaker2':
1347                                 if not hasattr(self._platformMesh[machine], 'texture'):
1348                                         self._platformMesh[machine].texture = opengl.loadGLTexture('Ultimaker2backplate.png')
1349                                 glBindTexture(GL_TEXTURE_2D, self._platformMesh[machine].texture)
1350                                 glEnable(GL_TEXTURE_2D)
1351                                 glPushMatrix()
1352                                 glColor4f(1,1,1,1)
1353
1354                                 glTranslate(0,150,-5)
1355                                 h = 50
1356                                 d = 8
1357                                 w = 100
1358                                 glEnable(GL_BLEND)
1359                                 glBlendFunc(GL_DST_COLOR, GL_ZERO)
1360                                 glBegin(GL_QUADS)
1361                                 glTexCoord2f(1, 0)
1362                                 glVertex3f( w, 0, h)
1363                                 glTexCoord2f(0, 0)
1364                                 glVertex3f(-w, 0, h)
1365                                 glTexCoord2f(0, 1)
1366                                 glVertex3f(-w, 0, 0)
1367                                 glTexCoord2f(1, 1)
1368                                 glVertex3f( w, 0, 0)
1369
1370                                 glTexCoord2f(1, 0)
1371                                 glVertex3f(-w, d, h)
1372                                 glTexCoord2f(0, 0)
1373                                 glVertex3f( w, d, h)
1374                                 glTexCoord2f(0, 1)
1375                                 glVertex3f( w, d, 0)
1376                                 glTexCoord2f(1, 1)
1377                                 glVertex3f(-w, d, 0)
1378                                 glEnd()
1379                                 glDisable(GL_TEXTURE_2D)
1380                                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1381                                 glPopMatrix()
1382                 else:
1383                         glColor4f(0,0,0,1)
1384                         glLineWidth(3)
1385                         glBegin(GL_LINES)
1386                         glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1387                         glVertex3f(-size[0] / 2, -size[1] / 2, 10)
1388                         glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1389                         glVertex3f(-size[0] / 2+10, -size[1] / 2, 0)
1390                         glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1391                         glVertex3f(-size[0] / 2, -size[1] / 2+10, 0)
1392                         glEnd()
1393
1394                 glDepthMask(False)
1395
1396                 polys = profile.getMachineSizePolygons()
1397                 height = profile.getMachineSettingFloat('machine_height')
1398                 glBegin(GL_QUADS)
1399                 for n in xrange(0, len(polys[0])):
1400                         if n % 2 == 0:
1401                                 glColor4ub(5, 171, 231, 96)
1402                         else:
1403                                 glColor4ub(5, 171, 231, 64)
1404                         glVertex3f(polys[0][n][0], polys[0][n][1], height)
1405                         glVertex3f(polys[0][n][0], polys[0][n][1], 0)
1406                         glVertex3f(polys[0][n-1][0], polys[0][n-1][1], 0)
1407                         glVertex3f(polys[0][n-1][0], polys[0][n-1][1], height)
1408                 glEnd()
1409                 glColor4ub(5, 171, 231, 128)
1410                 glBegin(GL_TRIANGLE_FAN)
1411                 for p in polys[0][::-1]:
1412                         glVertex3f(p[0], p[1], height)
1413                 glEnd()
1414
1415                 #Draw checkerboard
1416                 if self._platformTexture is None:
1417                         self._platformTexture = opengl.loadGLTexture('checkerboard.png')
1418                         glBindTexture(GL_TEXTURE_2D, self._platformTexture)
1419                         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
1420                         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
1421                 glColor4f(1,1,1,0.5)
1422                 glBindTexture(GL_TEXTURE_2D, self._platformTexture)
1423                 glEnable(GL_TEXTURE_2D)
1424                 glBegin(GL_TRIANGLE_FAN)
1425                 for p in polys[0]:
1426                         glTexCoord2f(p[0]/20, p[1]/20)
1427                         glVertex3f(p[0], p[1], 0)
1428                 glEnd()
1429                 glDisable(GL_TEXTURE_2D)
1430                 glColor4ub(127, 127, 127, 200)
1431                 for poly in polys[1:]:
1432                         glBegin(GL_TRIANGLE_FAN)
1433                         for p in poly:
1434                                 glTexCoord2f(p[0]/20, p[1]/20)
1435                                 glVertex3f(p[0], p[1], 0)
1436                         glEnd()
1437
1438                 glDepthMask(True)
1439                 glDisable(GL_BLEND)
1440                 glDisable(GL_CULL_FACE)
1441
1442         def _generateGCodeVBOs(self, layer):
1443                 ret = []
1444                 for extrudeType in ['WALL-OUTER:0', 'WALL-OUTER:1', 'WALL-OUTER:2', 'WALL-OUTER:3', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1445                         if ':' in extrudeType:
1446                                 extruder = int(extrudeType[extrudeType.find(':')+1:])
1447                                 extrudeType = extrudeType[0:extrudeType.find(':')]
1448                         else:
1449                                 extruder = None
1450                         pointList = numpy.zeros((0,3), numpy.float32)
1451                         for path in layer:
1452                                 if path['type'] == 'extrude' and path['pathType'] == extrudeType and (extruder is None or path['extruder'] == extruder):
1453                                         a = path['points']
1454                                         a = numpy.concatenate((a[:-1], a[1:]), 1)
1455                                         a = a.reshape((len(a) * 2, 3))
1456                                         pointList = numpy.concatenate((pointList, a))
1457                         ret.append(opengl.GLVBO(pointList))
1458                 return ret
1459
1460         def _generateGCodeVBOs2(self, layer):
1461                 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
1462                 filamentArea = math.pi * filamentRadius * filamentRadius
1463                 useFilamentArea = profile.getMachineSetting('gcode_flavor') == 'UltiGCode'
1464
1465                 ret = []
1466                 for extrudeType in ['WALL-OUTER:0', 'WALL-OUTER:1', 'WALL-OUTER:2', 'WALL-OUTER:3', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1467                         if ':' in extrudeType:
1468                                 extruder = int(extrudeType[extrudeType.find(':')+1:])
1469                                 extrudeType = extrudeType[0:extrudeType.find(':')]
1470                         else:
1471                                 extruder = None
1472                         pointList = numpy.zeros((0,3), numpy.float32)
1473                         for path in layer:
1474                                 if path['type'] == 'extrude' and path['pathType'] == extrudeType and (extruder is None or path['extruder'] == extruder):
1475                                         a = path['points']
1476                                         if extrudeType == 'FILL':
1477                                                 a[:,2] += 0.01
1478
1479                                         normal = a[1:] - a[:-1]
1480                                         lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
1481                                         normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
1482                                         normal[:,2] /= lens
1483
1484                                         ePerDist = path['extrusion'][1:] / lens
1485                                         if useFilamentArea:
1486                                                 lineWidth = ePerDist / path['layerThickness'] / 2.0
1487                                         else:
1488                                                 lineWidth = ePerDist * (filamentArea / path['layerThickness'] / 2)
1489
1490                                         normal[:,0] *= lineWidth
1491                                         normal[:,1] *= lineWidth
1492
1493                                         b = numpy.zeros((len(a)-1, 0), numpy.float32)
1494                                         b = numpy.concatenate((b, a[1:] + normal), 1)
1495                                         b = numpy.concatenate((b, a[1:] - normal), 1)
1496                                         b = numpy.concatenate((b, a[:-1] - normal), 1)
1497                                         b = numpy.concatenate((b, a[:-1] + normal), 1)
1498                                         b = b.reshape((len(b) * 4, 3))
1499
1500                                         if len(a) > 2:
1501                                                 normal2 = normal[:-1] + normal[1:]
1502                                                 lens2 = numpy.sqrt(normal2[:,0]**2 + normal2[:,1]**2)
1503                                                 normal2[:,0] /= lens2
1504                                                 normal2[:,1] /= lens2
1505                                                 normal2[:,0] *= lineWidth[:-1]
1506                                                 normal2[:,1] *= lineWidth[:-1]
1507
1508                                                 c = numpy.zeros((len(a)-2, 0), numpy.float32)
1509                                                 c = numpy.concatenate((c, a[1:-1]), 1)
1510                                                 c = numpy.concatenate((c, a[1:-1]+normal[1:]), 1)
1511                                                 c = numpy.concatenate((c, a[1:-1]+normal2), 1)
1512                                                 c = numpy.concatenate((c, a[1:-1]+normal[:-1]), 1)
1513
1514                                                 c = numpy.concatenate((c, a[1:-1]), 1)
1515                                                 c = numpy.concatenate((c, a[1:-1]-normal[1:]), 1)
1516                                                 c = numpy.concatenate((c, a[1:-1]-normal2), 1)
1517                                                 c = numpy.concatenate((c, a[1:-1]-normal[:-1]), 1)
1518
1519                                                 c = c.reshape((len(c) * 8, 3))
1520
1521                                                 pointList = numpy.concatenate((pointList, b, c))
1522                                         else:
1523                                                 pointList = numpy.concatenate((pointList, b))
1524                         ret.append(opengl.GLVBO(pointList))
1525
1526                 pointList = numpy.zeros((0,3), numpy.float32)
1527                 for path in layer:
1528                         if path['type'] == 'move':
1529                                 a = path['points'] + numpy.array([0,0,0.01], numpy.float32)
1530                                 a = numpy.concatenate((a[:-1], a[1:]), 1)
1531                                 a = a.reshape((len(a) * 2, 3))
1532                                 pointList = numpy.concatenate((pointList, a))
1533                         if path['type'] == 'retract':
1534                                 a = path['points'] + numpy.array([0,0,0.01], numpy.float32)
1535                                 a = numpy.concatenate((a[:-1], a[1:] + numpy.array([0,0,1], numpy.float32)), 1)
1536                                 a = a.reshape((len(a) * 2, 3))
1537                                 pointList = numpy.concatenate((pointList, a))
1538                 ret.append(opengl.GLVBO(pointList))
1539
1540                 return ret
1541
1542         def getObjectCenterPos(self):
1543                 if self._selectedObj is None:
1544                         return [0.0, 0.0, 0.0]
1545                 pos = self._selectedObj.getPosition()
1546                 size = self._selectedObj.getSize()
1547                 return [pos[0], pos[1], size[2]/2 - profile.getProfileSettingFloat('object_sink')]
1548
1549         def getObjectBoundaryCircle(self):
1550                 if self._selectedObj is None:
1551                         return 0.0
1552                 return self._selectedObj.getBoundaryCircle()
1553
1554         def getObjectSize(self):
1555                 if self._selectedObj is None:
1556                         return [0.0, 0.0, 0.0]
1557                 return self._selectedObj.getSize()
1558
1559         def getObjectMatrix(self):
1560                 if self._selectedObj is None:
1561                         return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1562                 return self._selectedObj.getMatrix()
1563
1564 class shaderEditor(wx.Dialog):
1565         def __init__(self, parent, callback, v, f):
1566                 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1567                 self._callback = callback
1568                 s = wx.BoxSizer(wx.VERTICAL)
1569                 self.SetSizer(s)
1570                 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1571                 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1572                 s.Add(self._vertex, 1, flag=wx.EXPAND)
1573                 s.Add(self._fragment, 1, flag=wx.EXPAND)
1574
1575                 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1576                 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1577
1578                 self.SetPosition(self.GetParent().GetPosition())
1579                 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1580                 self.Show()
1581
1582         def OnText(self, e):
1583                 self._callback(self._vertex.GetValue(), self._fragment.GetValue())