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