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