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