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