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