chiark / gitweb /
Put SD card code in a seperate file.
[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
8 import OpenGL
9 OpenGL.ERROR_CHECKING = False
10 from OpenGL.GLU import *
11 from OpenGL.GL import *
12
13 from Cura.gui import printWindow
14 from Cura.util import profile
15 from Cura.util import meshLoader
16 from Cura.util import objectScene
17 from Cura.util import resources
18 from Cura.util import sliceEngine
19 from Cura.util import machineCom
20 from Cura.gui.util import opengl
21 from Cura.gui.util import openglGui
22
23 class anim(object):
24         def __init__(self, start, end, runTime):
25                 self._start = start
26                 self._end = end
27                 self._startTime = time.time()
28                 self._runTime = runTime
29
30         def isDone(self):
31                 return time.time() > self._startTime + self._runTime
32
33         def getPosition(self):
34                 if self.isDone():
35                         return self._end
36                 f = (time.time() - self._startTime) / self._runTime
37                 ts = f*f
38                 tc = f*f*f
39                 #f = 6*tc*ts + -15*ts*ts + 10*tc
40                 f = tc + -3*ts + 3*f
41                 return self._start + (self._end - self._start) * f
42
43 class SceneView(openglGui.glGuiPanel):
44         def __init__(self, parent):
45                 super(SceneView, self).__init__(parent)
46
47                 self._yaw = 30
48                 self._pitch = 60
49                 self._zoom = 300
50                 self._scene = objectScene.Scene()
51                 self._objectShader = None
52                 self._focusObj = None
53                 self._selectedObj = None
54                 self._objColors = [None,None,None,None]
55                 self._mouseX = -1
56                 self._mouseY = -1
57                 self._mouseState = None
58                 self._viewTarget = numpy.array([0,0,0], numpy.float32)
59                 self._animView = None
60                 self._animZoom = None
61                 self._platformMesh = meshLoader.loadMeshes(resources.getPathForMesh('ultimaker_platform.stl'))[0]
62                 self._platformMesh._drawOffset = numpy.array([0,0,0.5], numpy.float32)
63                 self._isSimpleMode = True
64
65                 self.openFileButton      = openglGui.glButton(self, 4, 'Load', (0,0), self.ShowLoadModel)
66                 self.printButton         = openglGui.glButton(self, 6, 'Print', (1,0), self.ShowPrintWindow)
67                 self.printButton.setDisabled(True)
68
69                 self._slicer = sliceEngine.Slicer(self._updateSliceProgress)
70                 self._sceneUpdateTimer = wx.Timer(self)
71                 self.Bind(wx.EVT_TIMER, lambda e : self._slicer.runSlicer(self._scene), self._sceneUpdateTimer)
72
73                 self.updateProfileToControls()
74                 wx.EVT_IDLE(self, self.OnIdle)
75
76         def ShowLoadModel(self):
77                 dlg=wx.FileDialog(self, 'Open 3D model', os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
78                 dlg.SetWildcard(meshLoader.wildcardFilter())
79                 if dlg.ShowModal() != wx.ID_OK:
80                         dlg.Destroy()
81                         return
82                 filename = dlg.GetPath()
83                 dlg.Destroy()
84                 if not(os.path.exists(filename)):
85                         return False
86                 profile.putPreference('lastFile', filename)
87                 self.GetParent().GetParent().GetParent().addToModelMRU(filename)
88                 self.loadScene([filename])
89
90         def ShowPrintWindow(self):
91                 if machineCom.machineIsConnected():
92                         printWindow.printFile(self._slicer.getGCodeFilename())
93
94         def OnIdle(self, e):
95                 if self._animView is not None or self._animZoom is not None:
96                         self.Refresh()
97                         return
98                 for obj in self._scene.objects():
99                         if obj._loadAnim is not None:
100                                 self.Refresh()
101                                 return
102
103         def sceneUpdated(self):
104                 self._sceneUpdateTimer.Start(1, True)
105                 self._slicer.abortSlicer()
106                 self.Refresh()
107
108         def _updateSliceProgress(self, progressValue, ready):
109                 self.printButton.setDisabled(not ready)
110                 self.printButton.setProgressBar(progressValue)
111                 self.Refresh()
112
113         def loadScene(self, fileList):
114                 for filename in fileList:
115                         for obj in meshLoader.loadMeshes(filename):
116                                 obj._loadAnim = anim(1, 0, 1.5)
117                                 self._scene.add(obj)
118                                 self._selectObject(obj)
119                 self.sceneUpdated()
120
121         def _deleteObject(self, obj):
122                 if obj == self._selectedObj:
123                         self._selectedObj = None
124                 if obj == self._focusObj:
125                         self._focusObj = None
126                 self._scene.remove(obj)
127                 for m in obj._meshList:
128                         if m.vbo is not None:
129                                 self.glReleaseList.append(m.vbo)
130                 if self._isSimpleMode:
131                         self._scene.arrangeAll()
132                 self.sceneUpdated()
133
134         def _selectObject(self, obj, zoom = True):
135                 if obj != self._selectedObj:
136                         self._selectedObj = obj
137                 if zoom:
138                         newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getMaximum()[2] / 2])
139                         self._animView = anim(self._viewTarget.copy(), newViewPos, 0.5)
140                         newZoom = obj.getBoundaryCircle() * 6
141                         if newZoom > numpy.max(self._machineSize) * 3:
142                                 newZoom = numpy.max(self._machineSize) * 3
143                         self._animZoom = anim(self._zoom, newZoom, 0.5)
144
145         def updateProfileToControls(self):
146                 oldSimpleMode = self._isSimpleMode
147                 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
148                 if self._isSimpleMode and not oldSimpleMode:
149                         self._scene.arrangeAll()
150                         self.sceneUpdated()
151                 self._machineSize = numpy.array([profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')])
152                 self._objColors[0] = profile.getPreferenceColour('model_colour')
153                 self._objColors[1] = profile.getPreferenceColour('model_colour2')
154                 self._objColors[2] = profile.getPreferenceColour('model_colour3')
155                 self._objColors[3] = profile.getPreferenceColour('model_colour4')
156                 self._scene.setMachineSize(self._machineSize)
157
158         def OnKeyChar(self, keyCode):
159                 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE:
160                         if self._selectedObj is not None:
161                                 self._deleteObject(self._selectedObj)
162                                 self.Refresh()
163
164                 if keyCode == wx.WXK_F3:
165                         shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
166
167         def ShaderUpdate(self, v, f):
168                 s = opengl.GLShader(v, f)
169                 if s.isValid():
170                         self._objectLoadShader.release()
171                         self._objectLoadShader = s
172                         for obj in self._scene.objects():
173                                 obj._loadAnim = anim(1, 0, 1.5)
174                         self.Refresh()
175
176         def OnMouseDown(self,e):
177                 self._mouseX = e.GetX()
178                 self._mouseY = e.GetY()
179                 self._mouseClick3DPos = self._mouse3Dpos
180                 if e.ButtonDClick():
181                         self._mouseState = 'doubleClick'
182                 else:
183                         self._mouseState = 'dragOrClick'
184                 if self._mouseState == 'dragOrClick':
185                         if e.Button == 1:
186                                 if self._focusObj is not None:
187                                         self._selectObject(self._focusObj, False)
188                                         self.Refresh()
189
190         def OnMouseUp(self, e):
191                 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
192                         return
193                 if self._mouseState == 'dragOrClick':
194                         if e.Button == 1:
195                                 if self._focusObj is not None:
196                                         self._selectObject(self._focusObj)
197                                 else:
198                                         self._selectedObj = None
199                                         self.Refresh()
200                         if e.Button == 3 and self._selectedObj == self._focusObj:
201                                 #menu = wx.Menu()
202                                 #menu.Append(-1, 'Test')
203                                 #self.PopupMenu(menu)
204                                 #menu.Destroy()
205                                 pass
206                 if self._mouseState == 'dragObject' and self._selectedObj is not None:
207                         self._scene.pushFree()
208                         self.sceneUpdated()
209                 self._mouseState = None
210
211         def OnMouseMotion(self,e):
212                 if e.Dragging() and self._mouseState is not None:
213                         self._mouseState = 'drag'
214                         if not e.LeftIsDown() and e.RightIsDown():
215                                 self._yaw += e.GetX() - self._mouseX
216                                 self._pitch -= e.GetY() - self._mouseY
217                                 if self._pitch > 170:
218                                         self._pitch = 170
219                                 if self._pitch < 10:
220                                         self._pitch = 10
221                         elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
222                                 self._zoom += e.GetY() - self._mouseY
223                                 if self._zoom < 1:
224                                         self._zoom = 1
225                                 if self._zoom > numpy.max(self._machineSize) * 3:
226                                         self._zoom = numpy.max(self._machineSize) * 3
227                         elif e.LeftIsDown() and self._selectedObj is not None and not self._isSimpleMode:
228                                 self._mouseState = 'dragObject'
229                                 z = max(0, self._mouseClick3DPos[2])
230                                 p0 = opengl.unproject(self._mouseX, self.viewport[1] + self.viewport[3] - self._mouseY, 0, self.modelMatrix, self.projMatrix, self.viewport)
231                                 p1 = opengl.unproject(self._mouseX, self.viewport[1] + self.viewport[3] - self._mouseY, 1, self.modelMatrix, self.projMatrix, self.viewport)
232                                 p2 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport)
233                                 p3 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport)
234                                 p0[2] -= z
235                                 p1[2] -= z
236                                 p2[2] -= z
237                                 p3[2] -= z
238                                 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
239                                 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
240                                 diff = cursorZ1 - cursorZ0
241                                 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
242
243                 self._mouseX = e.GetX()
244                 self._mouseY = e.GetY()
245
246         def _init3DView(self):
247                 # set viewing projection
248                 size = self.GetSize()
249                 glViewport(0, 0, size.GetWidth(), size.GetHeight())
250                 glLoadIdentity()
251
252                 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
253
254                 glDisable(GL_RESCALE_NORMAL)
255                 glDisable(GL_LIGHTING)
256                 glDisable(GL_LIGHT0)
257                 glEnable(GL_DEPTH_TEST)
258                 glDisable(GL_CULL_FACE)
259                 glDisable(GL_BLEND)
260                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
261
262                 glClearColor(0.8, 0.8, 0.8, 1.0)
263                 glClearStencil(0)
264                 glClearDepth(1.0)
265
266                 glMatrixMode(GL_PROJECTION)
267                 glLoadIdentity()
268                 aspect = float(size.GetWidth()) / float(size.GetHeight())
269                 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
270
271                 glMatrixMode(GL_MODELVIEW)
272                 glLoadIdentity()
273                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
274
275         def OnPaint(self,e):
276                 if machineCom.machineIsConnected():
277                         self.printButton._imageID = 6
278                 else:
279                         self.printButton._imageID = 3
280
281                 if self._animView is not None:
282                         self._viewTarget = self._animView.getPosition()
283                         if self._animView.isDone():
284                                 self._animView = None
285                 if self._animZoom is not None:
286                         self._zoom = self._animZoom.getPosition()
287                         if self._animZoom.isDone():
288                                 self._animZoom = None
289                 if self._objectShader is None:
290                         self._objectShader = opengl.GLShader("""
291 uniform float cameraDistance;
292 varying float light_amount;
293
294 void main(void)
295 {
296     gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
297     gl_FrontColor = gl_Color;
298
299         light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
300         light_amount *= 1 - (length(gl_Position.xyz - vec3(0,0,cameraDistance)) / 1.5 / cameraDistance);
301         light_amount += 0.2;
302 }
303                         ""","""
304 uniform float cameraDistance;
305 varying float light_amount;
306
307 void main(void)
308 {
309         gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
310 }
311                         """)
312                         self._objectLoadShader = opengl.GLShader("""
313 uniform float cameraDistance;
314 uniform float intensity;
315 uniform float scale;
316 varying float light_amount;
317
318 void main(void)
319 {
320         vec4 tmp = gl_Vertex;
321     tmp.x += sin(tmp.z/5+intensity*30) * scale * intensity;
322     tmp.y += sin(tmp.z/3+intensity*40) * scale * intensity;
323     gl_Position = gl_ModelViewProjectionMatrix * tmp;
324     gl_FrontColor = gl_Color;
325
326         light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
327         light_amount *= 1 - (length(gl_Position.xyz - vec3(0,0,cameraDistance)) / 1.5 / cameraDistance);
328         light_amount += 0.2;
329 }
330                         ""","""
331 uniform float cameraDistance;
332 uniform float intensity;
333 varying float light_amount;
334
335 void main(void)
336 {
337         gl_FragColor = vec4(gl_Color.xyz * light_amount, 1-intensity);
338 }
339                         """)
340                 self._init3DView()
341                 glTranslate(0,0,-self._zoom)
342                 glRotate(-self._pitch, 1,0,0)
343                 glRotate(self._yaw, 0,0,1)
344                 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
345
346                 self.viewport = glGetIntegerv(GL_VIEWPORT)
347                 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
348                 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
349
350                 glClearColor(1,1,1,1)
351                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
352
353                 for n in xrange(0, len(self._scene.objects())):
354                         obj = self._scene.objects()[n]
355                         glColor4ub((n >> 24) & 0xFF, (n >> 16) & 0xFF, (n >> 8) & 0xFF, n & 0xFF)
356                         self._renderObject(obj)
357
358                 if self._mouseX > -1:
359                         n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0]
360                         if n < len(self._scene.objects()):
361                                 self._focusObj = self._scene.objects()[n]
362                         else:
363                                 self._focusObj = None
364                         f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
365                         self._mouse3Dpos = opengl.unproject(self._mouseX, self.viewport[1] + self.viewport[3] - self._mouseY, f, self.modelMatrix, self.projMatrix, self.viewport)
366
367                 self._init3DView()
368                 glTranslate(0,0,-self._zoom)
369                 glRotate(-self._pitch, 1,0,0)
370                 glRotate(self._yaw, 0,0,1)
371                 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
372
373                 glStencilFunc(GL_ALWAYS, 1, 1)
374                 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
375                 self._objectShader.bind()
376                 self._objectShader.setUniform('cameraDistance', self._zoom)
377                 for obj in self._scene.objects():
378                         if obj._loadAnim is not None:
379                                 if obj._loadAnim.isDone():
380                                         obj._loadAnim = None
381                                 else:
382                                         continue
383                         col = self._objColors[0]
384                         if not self._scene.checkPlatform(obj):
385                                 col = [0.5,0.5,0.5,0.8]
386                         glDisable(GL_STENCIL_TEST)
387                         if self._selectedObj == obj:
388                                 col = map(lambda n: n * 1.5, col)
389                                 glEnable(GL_STENCIL_TEST)
390                         elif self._focusObj == obj:
391                                 col = map(lambda n: n * 1.2, col)
392                         elif self._focusObj is not None or  self._selectedObj is not None:
393                                 col = map(lambda n: n * 0.8, col)
394                         glColor4f(col[0], col[1], col[2], col[3])
395                         self._renderObject(obj)
396                 self._objectShader.unbind()
397
398                 glDisable(GL_STENCIL_TEST)
399                 glEnable(GL_BLEND)
400                 self._objectLoadShader.bind()
401                 self._objectLoadShader.setUniform('cameraDistance', self._zoom)
402                 glColor4f(0.2, 0.6, 1.0, 1.0)
403                 for obj in self._scene.objects():
404                         if obj._loadAnim is None:
405                                 continue
406                         self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
407                         self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
408                         self._renderObject(obj)
409                 self._objectLoadShader.unbind()
410                 glDisable(GL_BLEND)
411
412                 self._drawMachine()
413
414                 #Draw the outline of the selected object, on top of everything else except the GUI.
415                 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
416                         glDisable(GL_DEPTH_TEST)
417                         glEnable(GL_CULL_FACE)
418                         glEnable(GL_STENCIL_TEST)
419                         glStencilFunc(GL_EQUAL, 0, 255)
420                         glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
421                         glLineWidth(2)
422                         glColor4f(1,1,1,0.5)
423                         self._renderObject(self._selectedObj)
424                         glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
425                         glDisable(GL_STENCIL_TEST)
426                         glDisable(GL_CULL_FACE)
427                         glEnable(GL_DEPTH_TEST)
428
429         def _renderObject(self, obj):
430                 glPushMatrix()
431                 glTranslate(obj.getPosition()[0], obj.getPosition()[1], 0)
432                 offset = obj.getDrawOffset()
433                 glTranslate(-offset[0], -offset[1], -offset[2])
434                 for m in obj._meshList:
435                         if m.vbo is None:
436                                 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
437                         m.vbo.render()
438                 glPopMatrix()
439
440         def _drawMachine(self):
441                 glEnable(GL_CULL_FACE)
442                 glEnable(GL_BLEND)
443
444                 if profile.getPreference('machine_type') == 'ultimaker':
445                         glColor4f(1,1,1,0.5)
446                         self._objectShader.bind()
447                         self._objectShader.setUniform('cameraDistance', self._zoom)
448                         self._renderObject(self._platformMesh)
449                         self._objectShader.unbind()
450
451                 size = [profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')]
452                 v0 = [ size[0] / 2, size[1] / 2, size[2]]
453                 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
454                 v2 = [-size[0] / 2, size[1] / 2, size[2]]
455                 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
456                 v4 = [ size[0] / 2, size[1] / 2, 0]
457                 v5 = [ size[0] / 2,-size[1] / 2, 0]
458                 v6 = [-size[0] / 2, size[1] / 2, 0]
459                 v7 = [-size[0] / 2,-size[1] / 2, 0]
460
461                 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
462                 glEnableClientState(GL_VERTEX_ARRAY)
463                 glVertexPointer(3, GL_FLOAT, 3*4, vList)
464
465                 glColor4ub(5, 171, 231, 64)
466                 glDrawArrays(GL_QUADS, 0, 4)
467                 glColor4ub(5, 171, 231, 96)
468                 glDrawArrays(GL_QUADS, 4, 8)
469                 glColor4ub(5, 171, 231, 128)
470                 glDrawArrays(GL_QUADS, 12, 8)
471
472                 sx = self._machineSize[0]
473                 sy = self._machineSize[1]
474                 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
475                         for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
476                                 x1 = x * 10
477                                 x2 = x1 + 10
478                                 y1 = y * 10
479                                 y2 = y1 + 10
480                                 x1 = max(min(x1, sx/2), -sx/2)
481                                 y1 = max(min(y1, sy/2), -sy/2)
482                                 x2 = max(min(x2, sx/2), -sx/2)
483                                 y2 = max(min(y2, sy/2), -sy/2)
484                                 if (x & 1) == (y & 1):
485                                         glColor4ub(5, 171, 231, 127)
486                                 else:
487                                         glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
488                                 glBegin(GL_QUADS)
489                                 glVertex3f(x1, y1, -0.02)
490                                 glVertex3f(x2, y1, -0.02)
491                                 glVertex3f(x2, y2, -0.02)
492                                 glVertex3f(x1, y2, -0.02)
493                                 glEnd()
494
495                 glDisableClientState(GL_VERTEX_ARRAY)
496                 glDisable(GL_BLEND)
497                 glDisable(GL_CULL_FACE)
498
499 class shaderEditor(wx.Dialog):
500         def __init__(self, parent, callback, v, f):
501                 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
502                 self._callback = callback
503                 s = wx.BoxSizer(wx.VERTICAL)
504                 self.SetSizer(s)
505                 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
506                 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
507                 s.Add(self._vertex, 1, flag=wx.EXPAND)
508                 s.Add(self._fragment, 1, flag=wx.EXPAND)
509
510                 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
511                 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
512
513                 self.SetPosition(self.GetParent().GetPosition())
514                 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
515                 self.Show()
516
517         def OnText(self, e):
518                 self._callback(self._vertex.GetValue(), self._fragment.GetValue())