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