chiark / gitweb /
0950cb6279a903445479d40473bdbdae6e1cb6da
[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.gui.util import opengl
15 from Cura.gui.util import openglGui
16
17 class anim(object):
18         def __init__(self, start, end, runTime):
19                 self._start = start
20                 self._end = end
21                 self._startTime = time.time()
22                 self._runTime = runTime
23
24         def isDone(self):
25                 return time.time() > self._startTime + self._runTime
26
27         def getPosition(self):
28                 if self.isDone():
29                         return self._end
30                 f = (time.time() - self._startTime) / self._runTime
31                 ts = f*f
32                 tc = f*f*f
33                 f = 6*tc*ts + -15*ts*ts + 10*tc
34                 return self._start + (self._end - self._start) * f
35
36 class SceneView(openglGui.glGuiPanel):
37         def __init__(self, parent):
38                 super(SceneView, self).__init__(parent)
39
40                 self._yaw = 30
41                 self._pitch = 60
42                 self._zoom = 300
43                 self._objectList = []
44                 self._objectShader = None
45                 self._focusObj = None
46                 self._selectedObj = None
47                 self._objColors = [None,None,None,None]
48                 self._mouseX = -1
49                 self._mouseY = -1
50                 self._mouseState = None
51                 self._viewTarget = numpy.array([0,0,0], numpy.float32);
52                 self._animView = None
53                 self._animZoom = None
54                 wx.EVT_IDLE(self, self.OnIdle)
55                 self.updateProfileToControls()
56
57         def OnIdle(self, e):
58                 if self._animView is not None or self._animZoom is not None:
59                         self.Refresh()
60
61         def loadScene(self, fileList):
62                 for filename in fileList:
63                         for obj in meshLoader.loadMeshes(filename):
64                                 self._objectList.append(obj)
65
66         def _deleteObject(self, obj):
67                 if obj == self._selectedObj:
68                         self._selectedObj = None
69                 if obj == self._focusObj:
70                         self._focusObj = None
71                 self._objectList.remove(obj)
72                 for m in obj._meshList:
73                         if m.vbo is not None:
74                                 self.glReleaseList.append(m.vbo)
75
76         def updateProfileToControls(self):
77                 self._machineSize = numpy.array([profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')])
78                 self._objColors[0] = profile.getPreferenceColour('model_colour')
79                 self._objColors[1] = profile.getPreferenceColour('model_colour2')
80                 self._objColors[2] = profile.getPreferenceColour('model_colour3')
81                 self._objColors[3] = profile.getPreferenceColour('model_colour4')
82
83         def OnKeyChar(self, keyCode):
84                 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE:
85                         if self._selectedObj is not None:
86                                 self._deleteObject(self._selectedObj)
87                                 self.Refresh()
88
89         def OnMouseDown(self,e):
90                 self._mouseX = e.GetX()
91                 self._mouseY = e.GetY()
92                 if e.ButtonDClick():
93                         self._mouseState = 'doubleClick'
94                 else:
95                         self._mouseState = 'dragOrClick'
96
97         def OnMouseUp(self, e):
98                 if self._mouseState == 'dragOrClick':
99                         if e.Button == 1:
100                                 if self._focusObj is not None:
101                                         self._selectedObj = self._focusObj
102                                         newViewPos = numpy.array([self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], self._selectedObj.getMaximum()[2] / 2])
103                                         self._animView = anim(self._viewTarget.copy(), newViewPos, 0.5)
104                                 else:
105                                         self._selectedObj = None
106                                         self.Refresh()
107                 elif self._mouseState == 'doubleClick':
108                         if self._selectedObj is not None:
109                                 newZoom = numpy.linalg.norm(self._selectedObj.getSize()) * 2
110                                 self._animZoom = anim(self._zoom, newZoom, 0.5)
111                 self._mouseState = None
112
113         def OnMouseMotion(self,e):
114                 if e.Dragging():
115                         self._mouseState = 'drag'
116                         if not e.LeftIsDown() and e.RightIsDown():
117                                 self._yaw += e.GetX() - self._mouseX
118                                 self._pitch -= e.GetY() - self._mouseY
119                                 if self._pitch > 170:
120                                         self._pitch = 170
121                                 if self._pitch < 10:
122                                         self._pitch = 10
123                         if (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
124                                 self._zoom += e.GetY() - self._mouseY
125                                 if self._zoom < 1:
126                                         self._zoom = 1
127                                 if self._zoom > numpy.max(self._machineSize) * 3:
128                                         self._zoom = numpy.max(self._machineSize) * 3
129                 self._mouseX = e.GetX()
130                 self._mouseY = e.GetY()
131
132         def _init3DView(self):
133                 # set viewing projection
134                 size = self.GetSize()
135                 glViewport(0, 0, size.GetWidth(), size.GetHeight())
136                 glLoadIdentity()
137
138                 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
139
140                 glDisable(GL_RESCALE_NORMAL)
141                 glDisable(GL_LIGHTING)
142                 glDisable(GL_LIGHT0)
143                 glEnable(GL_DEPTH_TEST)
144                 glDisable(GL_CULL_FACE)
145                 glDisable(GL_BLEND)
146                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
147
148                 glClearColor(0.8, 0.8, 0.8, 1.0)
149                 glClearStencil(0)
150                 glClearDepth(1.0)
151
152                 glMatrixMode(GL_PROJECTION)
153                 glLoadIdentity()
154                 aspect = float(size.GetWidth()) / float(size.GetHeight())
155                 gluPerspective(45.0, aspect, 1.0, 1000.0)
156
157                 glMatrixMode(GL_MODELVIEW)
158                 glLoadIdentity()
159                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
160
161         def OnPaint(self,e):
162                 if self._animView is not None:
163                         self._viewTarget = self._animView.getPosition()
164                         if self._animView.isDone():
165                                 self._animView = None
166                 if self._animZoom is not None:
167                         self._zoom = self._animZoom.getPosition()
168                         if self._animZoom.isDone():
169                                 self._animZoom = None
170                 if self._objectShader is None:
171                         self._objectShader = opengl.GLShader("""
172 uniform float cameraDistance;
173 varying float light_amount;
174
175 void main(void)
176 {
177     gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
178     gl_FrontColor = gl_Color;
179
180         light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
181         light_amount *= 1 - (length(gl_Position.xyz - vec3(0,0,cameraDistance)) / 1.5 / cameraDistance);
182         light_amount += 0.2;
183 }
184                         ""","""
185 uniform float cameraDistance;
186 varying float light_amount;
187
188 void main(void)
189 {
190         gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
191 }
192                         """)
193                 self._init3DView()
194                 glTranslate(0,0,-self._zoom)
195                 glRotate(-self._pitch, 1,0,0)
196                 glRotate(self._yaw, 0,0,1)
197                 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
198                 glClearColor(1,1,1,1)
199                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
200
201                 for n in xrange(0, len(self._objectList)):
202                         obj = self._objectList[n]
203                         glColor4ub((n >> 24) & 0xFF, (n >> 16) & 0xFF, (n >> 8) & 0xFF, n & 0xFF)
204                         self._renderObject(obj)
205
206                 if self._mouseX > -1:
207                         n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0]
208                         if n < len(self._objectList):
209                                 self._focusObj = self._objectList[n]
210                         else:
211                                 self._focusObj = None
212
213                 self._init3DView()
214                 glTranslate(0,0,-self._zoom)
215                 glRotate(-self._pitch, 1,0,0)
216                 glRotate(self._yaw, 0,0,1)
217                 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
218
219                 self._objectShader.bind()
220                 self._objectShader.setUniform('cameraDistance', self._zoom)
221                 for obj in self._objectList:
222                         col = self._objColors[0]
223                         if self._selectedObj == obj:
224                                 col = map(lambda n: n * 1.5, col)
225                         elif self._focusObj == obj:
226                                 col = map(lambda n: n * 1.2, col)
227                         elif self._focusObj is not None or  self._selectedObj is not None:
228                                 col = map(lambda n: n * 0.8, col)
229                         glColor4f(col[0], col[1], col[2], col[3])
230                         self._renderObject(obj)
231                 self._objectShader.unbind()
232
233                 self._drawMachine()
234
235                 #Draw the outline of the selected object, on top of everything else except the GUI.
236                 if self._selectedObj is not None:
237                         glClear(GL_STENCIL_BUFFER_BIT)
238
239                         glDisable(GL_DEPTH_TEST)
240                         glEnable(GL_STENCIL_TEST)
241                         glStencilFunc(GL_ALWAYS, 1, 1)
242                         glStencilOp(GL_INCR, GL_INCR, GL_INCR)
243                         glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
244                         self._renderObject(self._selectedObj)
245
246                         glStencilFunc(GL_EQUAL, 0, 255)
247                         glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
248                         glPolygonMode(GL_FRONT, GL_NONE)
249                         glPolygonMode(GL_BACK, GL_LINE)
250                         glLineWidth(2)
251                         glColor4f(1,1,1,0.5)
252                         self._renderObject(self._selectedObj)
253                         glPolygonMode(GL_BACK, GL_FILL)
254                         glPolygonMode(GL_FRONT, GL_FILL)
255                         glDisable(GL_STENCIL_TEST)
256                         glEnable(GL_DEPTH_TEST)
257
258         def _renderObject(self, obj):
259                 glPushMatrix()
260                 glTranslate(obj.getPosition()[0], obj.getPosition()[1], 0)
261                 offset = obj.getDrawOffset()
262                 glTranslate(-offset[0], -offset[1], -offset[2])
263                 for m in obj._meshList:
264                         if m.vbo is None:
265                                 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
266                         m.vbo.render()
267                 glPopMatrix()
268
269         def _drawMachine(self):
270                 size = [profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')]
271                 v0 = [ size[0] / 2, size[1] / 2, size[2]]
272                 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
273                 v2 = [-size[0] / 2, size[1] / 2, size[2]]
274                 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
275                 v4 = [ size[0] / 2, size[1] / 2, 0]
276                 v5 = [ size[0] / 2,-size[1] / 2, 0]
277                 v6 = [-size[0] / 2, size[1] / 2, 0]
278                 v7 = [-size[0] / 2,-size[1] / 2, 0]
279
280                 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
281                 glEnable(GL_CULL_FACE)
282                 glEnable(GL_BLEND)
283                 glEnableClientState(GL_VERTEX_ARRAY)
284                 glVertexPointer(3, GL_FLOAT, 3*4, vList)
285
286                 glColor4ub(5, 171, 231, 64)
287                 glDrawArrays(GL_QUADS, 0, 4)
288                 glColor4ub(5, 171, 231, 96)
289                 glDrawArrays(GL_QUADS, 4, 8)
290                 glColor4ub(5, 171, 231, 128)
291                 glDrawArrays(GL_QUADS, 12, 8)
292
293                 sx = self._machineSize[0]
294                 sy = self._machineSize[1]
295                 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
296                         for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
297                                 x1 = x * 10
298                                 x2 = x1 + 10
299                                 y1 = y * 10
300                                 y2 = y1 + 10
301                                 x1 = max(min(x1, sx/2), -sx/2)
302                                 y1 = max(min(y1, sy/2), -sy/2)
303                                 x2 = max(min(x2, sx/2), -sx/2)
304                                 y2 = max(min(y2, sy/2), -sy/2)
305                                 if (x & 1) == (y & 1):
306                                         glColor4ub(5, 171, 231, 127)
307                                 else:
308                                         glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
309                                 glBegin(GL_QUADS)
310                                 glVertex3f(x1, y1, -0.02)
311                                 glVertex3f(x2, y1, -0.02)
312                                 glVertex3f(x2, y2, -0.02)
313                                 glVertex3f(x1, y2, -0.02)
314                                 glEnd()
315
316                 glDisableClientState(GL_VERTEX_ARRAY)
317                 glDisable(GL_BLEND)
318                 glDisable(GL_CULL_FACE)