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