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