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