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