chiark / gitweb /
Merge branch 'master' into SteamEngine
[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, lambda e : self._slicer.runSlicer(self._scene), 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 _updateSliceProgress(self, progressValue, ready):
341                 self.printButton.setDisabled(not ready)
342                 self.printButton.setProgressBar(progressValue)
343                 if self._gcode is not None:
344                         self._gcode = None
345                         for layerVBOlist in self._gcodeVBOs:
346                                 for vbo in layerVBOlist:
347                                         self.glReleaseList.append(vbo)
348                         self._gcodeVBOs = []
349                 if ready:
350                         self._gcode = gcodeInterpreter.gcode()
351                         self._gcode.progressCallback = self._gcodeLoadCallback
352                         self._thread = threading.Thread(target=self._loadGCode)
353                         self._thread.daemon = True
354                         self._thread.start()
355                 self.QueueRefresh()
356
357         def _loadGCode(self):
358                 self._gcode.load(self._slicer.getGCodeFilename())
359
360         def _gcodeLoadCallback(self, progress):
361                 if self._gcode is None:
362                         return True
363                 if self.layerSelect.getValue() == self.layerSelect.getMaxValue():
364                         self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
365                         self.layerSelect.setValue(self.layerSelect.getMaxValue())
366                 else:
367                         self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
368                 return False
369
370         def loadScene(self, fileList):
371                 for filename in fileList:
372                         try:
373                                 objList = meshLoader.loadMeshes(filename)
374                         except:
375                                 traceback.print_exc()
376                         else:
377                                 for obj in objList:
378                                         obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
379                                         self._scene.add(obj)
380                                         self._scene.centerAll()
381                                         self._selectObject(obj)
382                 self.sceneUpdated()
383
384         def _deleteObject(self, obj):
385                 if obj == self._selectedObj:
386                         self._selectObject(None)
387                 if obj == self._focusObj:
388                         self._focusObj = None
389                 self._scene.remove(obj)
390                 for m in obj._meshList:
391                         if m.vbo is not None and m.vbo.decRef():
392                                 self.glReleaseList.append(m.vbo)
393                 if self._isSimpleMode:
394                         self._scene.arrangeAll()
395                 self.sceneUpdated()
396
397         def _selectObject(self, obj, zoom = True):
398                 if obj != self._selectedObj:
399                         self._selectedObj = obj
400                         self.updateProfileToControls()
401                         self.updateToolButtons()
402                 if zoom and obj is not None:
403                         newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getMaximum()[2] / 2])
404                         self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
405                         newZoom = obj.getBoundaryCircle() * 6
406                         if newZoom > numpy.max(self._machineSize) * 3:
407                                 newZoom = numpy.max(self._machineSize) * 3
408                         self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
409
410         def updateProfileToControls(self):
411                 oldSimpleMode = self._isSimpleMode
412                 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
413                 if self._isSimpleMode and not oldSimpleMode:
414                         self._scene.arrangeAll()
415                         self.sceneUpdated()
416                 self._machineSize = numpy.array([profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')])
417                 self._objColors[0] = profile.getPreferenceColour('model_colour')
418                 self._objColors[1] = profile.getPreferenceColour('model_colour2')
419                 self._objColors[2] = profile.getPreferenceColour('model_colour3')
420                 self._objColors[3] = profile.getPreferenceColour('model_colour4')
421                 self._scene.setMachineSize(self._machineSize)
422                 self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
423                 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'))
424
425                 if self._selectedObj is not None:
426                         scale = self._selectedObj.getScale()
427                         size = self._selectedObj.getSize()
428                         self.scaleXctrl.setValue(round(scale[0], 2))
429                         self.scaleYctrl.setValue(round(scale[1], 2))
430                         self.scaleZctrl.setValue(round(scale[2], 2))
431                         self.scaleXmmctrl.setValue(round(size[0], 2))
432                         self.scaleYmmctrl.setValue(round(size[1], 2))
433                         self.scaleZmmctrl.setValue(round(size[2], 2))
434
435         def OnKeyChar(self, keyCode):
436                 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE:
437                         if self._selectedObj is not None:
438                                 self._deleteObject(self._selectedObj)
439                                 self.QueueRefresh()
440                 if keyCode == wx.WXK_UP:
441                         self.layerSelect.setValue(self.layerSelect.getValue() + 1)
442                         self.QueueRefresh()
443                 elif keyCode == wx.WXK_DOWN:
444                         self.layerSelect.setValue(self.layerSelect.getValue() - 1)
445                         self.QueueRefresh()
446                 elif keyCode == wx.WXK_PAGEUP:
447                         self.layerSelect.setValue(self.layerSelect.getValue() + 10)
448                         self.QueueRefresh()
449                 elif keyCode == wx.WXK_PAGEDOWN:
450                         self.layerSelect.setValue(self.layerSelect.getValue() - 10)
451                         self.QueueRefresh()
452
453                 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
454                         shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
455
456         def ShaderUpdate(self, v, f):
457                 s = opengl.GLShader(v, f)
458                 if s.isValid():
459                         self._objectLoadShader.release()
460                         self._objectLoadShader = s
461                         for obj in self._scene.objects():
462                                 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
463                         self.QueueRefresh()
464
465         def OnMouseDown(self,e):
466                 self._mouseX = e.GetX()
467                 self._mouseY = e.GetY()
468                 self._mouseClick3DPos = self._mouse3Dpos
469                 self._mouseClickFocus = self._focusObj
470                 if e.ButtonDClick():
471                         self._mouseState = 'doubleClick'
472                 else:
473                         self._mouseState = 'dragOrClick'
474                 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
475                 p0 -= self.getObjectCenterPos() - self._viewTarget
476                 p1 -= self.getObjectCenterPos() - self._viewTarget
477                 if self.tool.OnDragStart(p0, p1):
478                         self._mouseState = 'tool'
479                 if self._mouseState == 'dragOrClick':
480                         if e.GetButton() == 1:
481                                 if self._focusObj is not None:
482                                         self._selectObject(self._focusObj, False)
483                                         self.QueueRefresh()
484
485         def OnMouseUp(self, e):
486                 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
487                         return
488                 if self._mouseState == 'dragOrClick':
489                         if e.GetButton() == 1:
490                                 self._selectObject(self._focusObj)
491                         if e.GetButton() == 3:
492                                         menu = wx.Menu()
493                                         if self._focusObj is not None:
494                                                 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, 'Delete'))
495                                                 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, 'Multiply'))
496                                                 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, 'Split'))
497                                         if self._selectedObj != self._focusObj and self._focusObj is not None and int(profile.getPreference('extruder_amount')) > 1:
498                                                 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, 'Dual extrusion merge'))
499                                         if len(self._scene.objects()) > 0:
500                                                 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, 'Delete all'))
501                                         if menu.MenuItemCount > 0:
502                                                 self.PopupMenu(menu)
503                                         menu.Destroy()
504                 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
505                         self._scene.pushFree()
506                         self.sceneUpdated()
507                 elif self._mouseState == 'tool':
508                         if self.tempMatrix is not None and self._selectedObj is not None:
509                                 self._selectedObj.applyMatrix(self.tempMatrix)
510                                 self._scene.pushFree()
511                                 self._selectObject(self._selectedObj)
512                         self.tempMatrix = None
513                         self.tool.OnDragEnd()
514                         self.sceneUpdated()
515                 self._mouseState = None
516
517         def OnMouseMotion(self,e):
518                 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
519                 p0 -= self.getObjectCenterPos() - self._viewTarget
520                 p1 -= self.getObjectCenterPos() - self._viewTarget
521
522                 if e.Dragging() and self._mouseState is not None:
523                         if self._mouseState == 'tool':
524                                 self.tool.OnDrag(p0, p1)
525                         elif not e.LeftIsDown() and e.RightIsDown():
526                                 self._mouseState = 'drag'
527                                 self._yaw += e.GetX() - self._mouseX
528                                 self._pitch -= e.GetY() - self._mouseY
529                                 if self._pitch > 170:
530                                         self._pitch = 170
531                                 if self._pitch < 10:
532                                         self._pitch = 10
533                         elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
534                                 self._mouseState = 'drag'
535                                 self._zoom += e.GetY() - self._mouseY
536                                 if self._zoom < 1:
537                                         self._zoom = 1
538                                 if self._zoom > numpy.max(self._machineSize) * 3:
539                                         self._zoom = numpy.max(self._machineSize) * 3
540                         elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus and not self._isSimpleMode:
541                                 self._mouseState = 'dragObject'
542                                 z = max(0, self._mouseClick3DPos[2])
543                                 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
544                                 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
545                                 p0[2] -= z
546                                 p1[2] -= z
547                                 p2[2] -= z
548                                 p3[2] -= z
549                                 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
550                                 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
551                                 diff = cursorZ1 - cursorZ0
552                                 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
553                 if not e.Dragging() or self._mouseState != 'tool':
554                         self.tool.OnMouseMove(p0, p1)
555
556                 self._mouseX = e.GetX()
557                 self._mouseY = e.GetY()
558
559         def OnMouseWheel(self, e):
560                 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
561                 delta = max(min(delta,4),-4)
562                 self._zoom *= 1.0 - delta / 10.0
563                 if self._zoom < 1.0:
564                         self._zoom = 1.0
565                 if self._zoom > numpy.max(self._machineSize) * 3:
566                         self._zoom = numpy.max(self._machineSize) * 3
567                 self.Refresh()
568
569         def getMouseRay(self, x, y):
570                 if self._viewport is None:
571                         return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
572                 p0 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
573                 p1 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
574                 p0 -= self._viewTarget
575                 p1 -= self._viewTarget
576                 return p0, p1
577
578         def _init3DView(self):
579                 # set viewing projection
580                 size = self.GetSize()
581                 glViewport(0, 0, size.GetWidth(), size.GetHeight())
582                 glLoadIdentity()
583
584                 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
585
586                 glDisable(GL_RESCALE_NORMAL)
587                 glDisable(GL_LIGHTING)
588                 glDisable(GL_LIGHT0)
589                 glEnable(GL_DEPTH_TEST)
590                 glDisable(GL_CULL_FACE)
591                 glDisable(GL_BLEND)
592                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
593
594                 glClearColor(0.8, 0.8, 0.8, 1.0)
595                 glClearStencil(0)
596                 glClearDepth(1.0)
597
598                 glMatrixMode(GL_PROJECTION)
599                 glLoadIdentity()
600                 aspect = float(size.GetWidth()) / float(size.GetHeight())
601                 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
602
603                 glMatrixMode(GL_MODELVIEW)
604                 glLoadIdentity()
605                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
606
607         def OnPaint(self,e):
608                 if machineCom.machineIsConnected():
609                         self.printButton._imageID = 6
610                         self.printButton._tooltip = 'Print'
611                 elif len(removableStorage.getPossibleSDcardDrives()) > 0:
612                         self.printButton._imageID = 2
613                         self.printButton._tooltip = 'Toolpath to SD'
614                 else:
615                         self.printButton._imageID = 3
616                         self.printButton._tooltip = 'Save toolpath'
617
618                 if self._animView is not None:
619                         self._viewTarget = self._animView.getPosition()
620                         if self._animView.isDone():
621                                 self._animView = None
622                 if self._animZoom is not None:
623                         self._zoom = self._animZoom.getPosition()
624                         if self._animZoom.isDone():
625                                 self._animZoom = None
626                 if self._objectShader is None:
627                         self._objectShader = opengl.GLShader("""
628 varying float light_amount;
629
630 void main(void)
631 {
632     gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
633     gl_FrontColor = gl_Color;
634
635         light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
636         light_amount += 0.2;
637 }
638                         ""","""
639 varying float light_amount;
640
641 void main(void)
642 {
643         gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
644 }
645                         """)
646                         self._objectLoadShader = opengl.GLShader("""
647 uniform float intensity;
648 uniform float scale;
649 varying float light_amount;
650
651 void main(void)
652 {
653         vec4 tmp = gl_Vertex;
654     tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
655     tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
656     gl_Position = gl_ModelViewProjectionMatrix * tmp;
657     gl_FrontColor = gl_Color;
658
659         light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
660         light_amount += 0.2;
661 }
662                         ""","""
663 uniform float intensity;
664 varying float light_amount;
665
666 void main(void)
667 {
668         gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
669 }
670                         """)
671                 self._init3DView()
672                 glTranslate(0,0,-self._zoom)
673                 glRotate(-self._pitch, 1,0,0)
674                 glRotate(self._yaw, 0,0,1)
675                 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
676
677                 self._viewport = glGetIntegerv(GL_VIEWPORT)
678                 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
679                 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
680
681                 glClearColor(1,1,1,1)
682                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
683
684                 if self.viewMode != 'gcode':
685                         for n in xrange(0, len(self._scene.objects())):
686                                 obj = self._scene.objects()[n]
687                                 glColor4ub((n >> 24) & 0xFF, (n >> 16) & 0xFF, (n >> 8) & 0xFF, n & 0xFF)
688                                 self._renderObject(obj)
689
690                 if self._mouseX > -1:
691                         n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0]
692                         if n < len(self._scene.objects()):
693                                 self._focusObj = self._scene.objects()[n]
694                         else:
695                                 self._focusObj = None
696                         f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
697                         self._mouse3Dpos = opengl.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
698                         self._mouse3Dpos -= self._viewTarget
699
700                 self._init3DView()
701                 glTranslate(0,0,-self._zoom)
702                 glRotate(-self._pitch, 1,0,0)
703                 glRotate(self._yaw, 0,0,1)
704                 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
705
706                 if self.viewMode == 'gcode':
707                         if self._gcode is not None:
708                                 glPushMatrix()
709                                 glTranslate(-self._machineSize[0] / 2, -self._machineSize[1] / 2, 0)
710                                 t = time.time()
711                                 drawUpTill = min(len(self._gcode.layerList), self.layerSelect.getValue() + 1)
712                                 for n in xrange(0, drawUpTill):
713                                         c = 1.0 - float(drawUpTill - n) / 15
714                                         c = max(0.3, c)
715                                         if len(self._gcodeVBOs) < n + 1:
716                                                 self._gcodeVBOs.append(self._generateGCodeVBOs(self._gcode.layerList[n]))
717                                                 if time.time() - t > 0.5:
718                                                         self.QueueRefresh()
719                                                         break
720                                         #['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']
721                                         if n == drawUpTill - 1:
722                                                 if len(self._gcodeVBOs[n]) < 6:
723                                                         self._gcodeVBOs[n] += self._generateGCodeVBOs2(self._gcode.layerList[n])
724                                                 glColor3f(c, 0, 0)
725                                                 self._gcodeVBOs[n][5].render(GL_QUADS)
726                                                 glColor3f(0, c, 0)
727                                                 self._gcodeVBOs[n][6].render(GL_QUADS)
728                                                 glColor3f(c/2, c/2, 0.0)
729                                                 self._gcodeVBOs[n][7].render(GL_QUADS)
730                                                 glColor3f(0, c, c)
731                                                 self._gcodeVBOs[n][8].render(GL_QUADS)
732                                                 self._gcodeVBOs[n][9].render(GL_QUADS)
733                                         else:
734                                                 glColor3f(c, 0, 0)
735                                                 self._gcodeVBOs[n][0].render(GL_LINES)
736                                                 glColor3f(0, c, 0)
737                                                 self._gcodeVBOs[n][1].render(GL_LINES)
738                                                 glColor3f(c/2, c/2, 0.0)
739                                                 self._gcodeVBOs[n][2].render(GL_LINES)
740                                                 glColor3f(0, c, c)
741                                                 self._gcodeVBOs[n][3].render(GL_LINES)
742                                                 self._gcodeVBOs[n][4].render(GL_LINES)
743                                 glPopMatrix()
744                 else:
745                         glStencilFunc(GL_ALWAYS, 1, 1)
746                         glStencilOp(GL_INCR, GL_INCR, GL_INCR)
747
748                         self._objectShader.bind()
749                         for obj in self._scene.objects():
750                                 if obj._loadAnim is not None:
751                                         if obj._loadAnim.isDone():
752                                                 obj._loadAnim = None
753                                         else:
754                                                 continue
755                                 brightness = 1.0
756                                 if self._selectedObj == obj:
757                                         #If we want transparent, then first render a solid black model to remove the printer size lines.
758                                         if self.viewMode == 'transparent':
759                                                 glColor4f(0, 0, 0, 0)
760                                                 self._renderObject(obj)
761                                                 glEnable(GL_BLEND)
762                                                 glBlendFunc(GL_ONE, GL_ONE)
763                                                 glDisable(GL_DEPTH_TEST)
764                                                 brightness *= 0.5
765                                         if self.viewMode == 'xray':
766                                                 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
767                                         glEnable(GL_STENCIL_TEST)
768                                 if self._focusObj == obj:
769                                         brightness = 1.2
770                                 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
771                                         brightness = 0.8
772                                 if not self._scene.checkPlatform(obj):
773                                         glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
774                                         self._renderObject(obj)
775                                 else:
776                                         self._renderObject(obj, brightness)
777                                 glDisable(GL_STENCIL_TEST)
778                                 glDisable(GL_BLEND)
779                                 glEnable(GL_DEPTH_TEST)
780                                 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
781
782                                 if obj == self._selectedObj and self.viewMode == 'xray':
783                                         glPushMatrix()
784                                         glLoadIdentity()
785                                         glEnable(GL_STENCIL_TEST)
786                                         glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP)
787                                         glDisable(GL_DEPTH_TEST)
788                                         for i in xrange(2, 15, 2):
789                                                 glStencilFunc(GL_EQUAL, i, 0xFF);
790                                                 glColor(float(i)/10, float(i)/10, float(i)/5)
791                                                 glBegin(GL_QUADS)
792                                                 glVertex3f(-1000,-1000,-1)
793                                                 glVertex3f( 1000,-1000,-1)
794                                                 glVertex3f( 1000, 1000,-1)
795                                                 glVertex3f(-1000, 1000,-1)
796                                                 glEnd()
797                                         for i in xrange(1, 15, 2):
798                                                 glStencilFunc(GL_EQUAL, i, 0xFF);
799                                                 glColor(float(i)/10, 0, 0)
800                                                 glBegin(GL_QUADS)
801                                                 glVertex3f(-1000,-1000,-1)
802                                                 glVertex3f( 1000,-1000,-1)
803                                                 glVertex3f( 1000, 1000,-1)
804                                                 glVertex3f(-1000, 1000,-1)
805                                                 glEnd()
806                                         glPopMatrix()
807                                         glDisable(GL_STENCIL_TEST)
808                                         glEnable(GL_DEPTH_TEST)
809
810                         self._objectShader.unbind()
811
812                         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
813                         glEnable(GL_BLEND)
814                         self._objectLoadShader.bind()
815                         glColor4f(0.2, 0.6, 1.0, 1.0)
816                         for obj in self._scene.objects():
817                                 if obj._loadAnim is None:
818                                         continue
819                                 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
820                                 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
821                                 self._renderObject(obj)
822                         self._objectLoadShader.unbind()
823                         glDisable(GL_BLEND)
824
825                 self._drawMachine()
826
827                 if self.viewMode == 'gcode':
828                         pass
829                 else:
830                         #Draw the object box-shadow, so you can see where it will collide with other objects.
831                         if self._selectedObj is not None and len(self._scene.objects()) > 1:
832                                 size = self._selectedObj.getSize()[0:2] / 2 + self._scene.getObjectExtend()
833                                 glPushMatrix()
834                                 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0.0)
835                                 glEnable(GL_BLEND)
836                                 glEnable(GL_CULL_FACE)
837                                 glColor4f(0,0,0,0.12)
838                                 glBegin(GL_QUADS)
839                                 glVertex3f(-size[0],  size[1], 0.1)
840                                 glVertex3f(-size[0], -size[1], 0.1)
841                                 glVertex3f( size[0], -size[1], 0.1)
842                                 glVertex3f( size[0],  size[1], 0.1)
843                                 glEnd()
844                                 glDisable(GL_CULL_FACE)
845                                 glPopMatrix()
846
847                         #Draw the outline of the selected object, on top of everything else except the GUI.
848                         if self._selectedObj is not None and self._selectedObj._loadAnim is None:
849                                 glDisable(GL_DEPTH_TEST)
850                                 glEnable(GL_CULL_FACE)
851                                 glEnable(GL_STENCIL_TEST)
852                                 glDisable(GL_BLEND)
853                                 glStencilFunc(GL_EQUAL, 0, 255)
854
855                                 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
856                                 glLineWidth(2)
857                                 glColor4f(1,1,1,0.5)
858                                 self._renderObject(self._selectedObj)
859                                 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
860
861                                 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
862                                 glDisable(GL_STENCIL_TEST)
863                                 glDisable(GL_CULL_FACE)
864                                 glEnable(GL_DEPTH_TEST)
865
866                         if self._selectedObj is not None:
867                                 glPushMatrix()
868                                 pos = self.getObjectCenterPos()
869                                 glTranslate(pos[0], pos[1], pos[2])
870                                 self.tool.OnDraw()
871                                 glPopMatrix()
872
873         def _renderObject(self, obj, brightness = False):
874                 glPushMatrix()
875                 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
876
877                 if self.tempMatrix is not None and obj == self._selectedObj:
878                         tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
879                         glMultMatrixf(tempMatrix)
880
881                 offset = obj.getDrawOffset()
882                 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
883
884                 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
885                 glMultMatrixf(tempMatrix)
886
887                 n = 0
888                 for m in obj._meshList:
889                         if m.vbo is None:
890                                 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
891                         if brightness:
892                                 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
893                                 n += 1
894                         m.vbo.render()
895                 glPopMatrix()
896
897         def _drawMachine(self):
898                 glEnable(GL_CULL_FACE)
899                 glEnable(GL_BLEND)
900
901                 if profile.getPreference('machine_type') == 'ultimaker':
902                         glColor4f(1,1,1,0.5)
903                         self._objectShader.bind()
904                         self._renderObject(self._platformMesh)
905                         self._objectShader.unbind()
906
907                 size = [profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')]
908                 v0 = [ size[0] / 2, size[1] / 2, size[2]]
909                 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
910                 v2 = [-size[0] / 2, size[1] / 2, size[2]]
911                 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
912                 v4 = [ size[0] / 2, size[1] / 2, 0]
913                 v5 = [ size[0] / 2,-size[1] / 2, 0]
914                 v6 = [-size[0] / 2, size[1] / 2, 0]
915                 v7 = [-size[0] / 2,-size[1] / 2, 0]
916
917                 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
918                 glEnableClientState(GL_VERTEX_ARRAY)
919                 glVertexPointer(3, GL_FLOAT, 3*4, vList)
920
921                 glColor4ub(5, 171, 231, 64)
922                 glDrawArrays(GL_QUADS, 0, 4)
923                 glColor4ub(5, 171, 231, 96)
924                 glDrawArrays(GL_QUADS, 4, 8)
925                 glColor4ub(5, 171, 231, 128)
926                 glDrawArrays(GL_QUADS, 12, 8)
927
928                 sx = self._machineSize[0]
929                 sy = self._machineSize[1]
930                 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
931                         for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
932                                 x1 = x * 10
933                                 x2 = x1 + 10
934                                 y1 = y * 10
935                                 y2 = y1 + 10
936                                 x1 = max(min(x1, sx/2), -sx/2)
937                                 y1 = max(min(y1, sy/2), -sy/2)
938                                 x2 = max(min(x2, sx/2), -sx/2)
939                                 y2 = max(min(y2, sy/2), -sy/2)
940                                 if (x & 1) == (y & 1):
941                                         glColor4ub(5, 171, 231, 127)
942                                 else:
943                                         glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
944                                 glBegin(GL_QUADS)
945                                 glVertex3f(x1, y1, -0.02)
946                                 glVertex3f(x2, y1, -0.02)
947                                 glVertex3f(x2, y2, -0.02)
948                                 glVertex3f(x1, y2, -0.02)
949                                 glEnd()
950
951                 glDisableClientState(GL_VERTEX_ARRAY)
952                 glDisable(GL_BLEND)
953                 glDisable(GL_CULL_FACE)
954
955         def _generateGCodeVBOs(self, layer):
956                 ret = []
957                 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
958                         pointList = numpy.zeros((0,3), numpy.float32)
959                         for path in layer:
960                                 if path.type == 'extrude' and path.pathType == extrudeType:
961                                         a = numpy.array(path.points, numpy.float32)
962                                         a = numpy.concatenate((a[:-1], a[1:]), 1)
963                                         a = a.reshape((len(a) * 2, 3))
964                                         pointList = numpy.concatenate((pointList, a))
965                         ret.append(opengl.GLVBO(pointList))
966                 return ret
967
968         def _generateGCodeVBOs2(self, layer):
969                 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
970                 filamentArea = math.pi * filamentRadius * filamentRadius
971
972                 ret = []
973                 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
974                         pointList = numpy.zeros((0,3), numpy.float32)
975                         for path in layer:
976                                 if path.type == 'extrude' and path.pathType == extrudeType:
977                                         a = numpy.array(path.points, numpy.float32)
978                                         if extrudeType == 'FILL':
979                                                 a[:,2] += 0.01
980
981                                         normal = a[1:] - a[:-1]
982                                         lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
983                                         normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
984                                         normal[:,2] /= lens
985
986                                         ePerDist = path.extrusion[1:] / lens
987                                         lineWidth = ePerDist * (filamentArea / path.layerThickness / 2)
988
989                                         normal[:,0] *= lineWidth
990                                         normal[:,1] *= lineWidth
991
992                                         b = numpy.zeros((len(a)-1, 0), numpy.float32)
993                                         b = numpy.concatenate((b, a[:-1] + normal), 1)
994                                         b = numpy.concatenate((b, a[1:] + normal), 1)
995                                         b = numpy.concatenate((b, a[1:] - normal), 1)
996                                         b = numpy.concatenate((b, a[:-1] - normal), 1)
997                                         b = b.reshape((len(b) * 4, 3))
998
999                                         pointList = numpy.concatenate((pointList, b))
1000                         ret.append(opengl.GLVBO(pointList))
1001                 return ret
1002
1003         def getObjectCenterPos(self):
1004                 if self._selectedObj is None:
1005                         return [0.0, 0.0, 0.0]
1006                 pos = self._selectedObj.getPosition()
1007                 size = self._selectedObj.getSize()
1008                 return [pos[0], pos[1], size[2]/2]
1009
1010         def getObjectBoundaryCircle(self):
1011                 if self._selectedObj is None:
1012                         return 0.0
1013                 return self._selectedObj.getBoundaryCircle()
1014
1015         def getObjectSize(self):
1016                 if self._selectedObj is None:
1017                         return [0.0, 0.0, 0.0]
1018                 return self._selectedObj.getSize()
1019
1020         def getObjectMatrix(self):
1021                 if self._selectedObj is None:
1022                         return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1023                 return self._selectedObj.getMatrix()
1024
1025 class shaderEditor(wx.Dialog):
1026         def __init__(self, parent, callback, v, f):
1027                 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1028                 self._callback = callback
1029                 s = wx.BoxSizer(wx.VERTICAL)
1030                 self.SetSizer(s)
1031                 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1032                 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1033                 s.Add(self._vertex, 1, flag=wx.EXPAND)
1034                 s.Add(self._fragment, 1, flag=wx.EXPAND)
1035
1036                 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1037                 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1038
1039                 self.SetPosition(self.GetParent().GetPosition())
1040                 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1041                 self.Show()
1042
1043         def OnText(self, e):
1044                 self._callback(self._vertex.GetValue(), self._fragment.GetValue())