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