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