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