chiark / gitweb /
Make the print button functional.
[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                 self.printButton._imageID = 6 if machineCom.machineIsConnected() else 2
277
278                 if self._animView is not None:
279                         self._viewTarget = self._animView.getPosition()
280                         if self._animView.isDone():
281                                 self._animView = None
282                 if self._animZoom is not None:
283                         self._zoom = self._animZoom.getPosition()
284                         if self._animZoom.isDone():
285                                 self._animZoom = None
286                 if self._objectShader is None:
287                         self._objectShader = opengl.GLShader("""
288 uniform float cameraDistance;
289 varying float light_amount;
290
291 void main(void)
292 {
293     gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
294     gl_FrontColor = gl_Color;
295
296         light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
297         light_amount *= 1 - (length(gl_Position.xyz - vec3(0,0,cameraDistance)) / 1.5 / cameraDistance);
298         light_amount += 0.2;
299 }
300                         ""","""
301 uniform float cameraDistance;
302 varying float light_amount;
303
304 void main(void)
305 {
306         gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
307 }
308                         """)
309                         self._objectLoadShader = opengl.GLShader("""
310 uniform float cameraDistance;
311 uniform float intensity;
312 uniform float scale;
313 varying float light_amount;
314
315 void main(void)
316 {
317         vec4 tmp = gl_Vertex;
318     tmp.x += sin(tmp.z/5+intensity*30) * scale * intensity;
319     tmp.y += sin(tmp.z/3+intensity*40) * scale * intensity;
320     gl_Position = gl_ModelViewProjectionMatrix * tmp;
321     gl_FrontColor = gl_Color;
322
323         light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
324         light_amount *= 1 - (length(gl_Position.xyz - vec3(0,0,cameraDistance)) / 1.5 / cameraDistance);
325         light_amount += 0.2;
326 }
327                         ""","""
328 uniform float cameraDistance;
329 uniform float intensity;
330 varying float light_amount;
331
332 void main(void)
333 {
334         gl_FragColor = vec4(gl_Color.xyz * light_amount, 1-intensity);
335 }
336                         """)
337                 self._init3DView()
338                 glTranslate(0,0,-self._zoom)
339                 glRotate(-self._pitch, 1,0,0)
340                 glRotate(self._yaw, 0,0,1)
341                 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
342
343                 self.viewport = glGetIntegerv(GL_VIEWPORT)
344                 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
345                 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
346
347                 glClearColor(1,1,1,1)
348                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
349
350                 for n in xrange(0, len(self._scene.objects())):
351                         obj = self._scene.objects()[n]
352                         glColor4ub((n >> 24) & 0xFF, (n >> 16) & 0xFF, (n >> 8) & 0xFF, n & 0xFF)
353                         self._renderObject(obj)
354
355                 if self._mouseX > -1:
356                         n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0]
357                         if n < len(self._scene.objects()):
358                                 self._focusObj = self._scene.objects()[n]
359                         else:
360                                 self._focusObj = None
361                         f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
362                         self._mouse3Dpos = opengl.unproject(self._mouseX, self.viewport[1] + self.viewport[3] - self._mouseY, f, self.modelMatrix, self.projMatrix, self.viewport)
363
364                 self._init3DView()
365                 glTranslate(0,0,-self._zoom)
366                 glRotate(-self._pitch, 1,0,0)
367                 glRotate(self._yaw, 0,0,1)
368                 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
369
370                 glStencilFunc(GL_ALWAYS, 1, 1)
371                 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
372                 self._objectShader.bind()
373                 self._objectShader.setUniform('cameraDistance', self._zoom)
374                 for obj in self._scene.objects():
375                         if obj._loadAnim is not None:
376                                 if obj._loadAnim.isDone():
377                                         obj._loadAnim = None
378                                 else:
379                                         continue
380                         col = self._objColors[0]
381                         if not self._scene.checkPlatform(obj):
382                                 col = [0.5,0.5,0.5,0.8]
383                         glDisable(GL_STENCIL_TEST)
384                         if self._selectedObj == obj:
385                                 col = map(lambda n: n * 1.5, col)
386                                 glEnable(GL_STENCIL_TEST)
387                         elif self._focusObj == obj:
388                                 col = map(lambda n: n * 1.2, col)
389                         elif self._focusObj is not None or  self._selectedObj is not None:
390                                 col = map(lambda n: n * 0.8, col)
391                         glColor4f(col[0], col[1], col[2], col[3])
392                         self._renderObject(obj)
393                 self._objectShader.unbind()
394
395                 glDisable(GL_STENCIL_TEST)
396                 glEnable(GL_BLEND)
397                 self._objectLoadShader.bind()
398                 self._objectLoadShader.setUniform('cameraDistance', self._zoom)
399                 glColor4f(0.2, 0.6, 1.0, 1.0)
400                 for obj in self._scene.objects():
401                         if obj._loadAnim is None:
402                                 continue
403                         self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
404                         self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
405                         self._renderObject(obj)
406                 self._objectLoadShader.unbind()
407                 glDisable(GL_BLEND)
408
409                 self._drawMachine()
410
411                 #Draw the outline of the selected object, on top of everything else except the GUI.
412                 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
413                         glDisable(GL_DEPTH_TEST)
414                         glEnable(GL_CULL_FACE)
415                         glEnable(GL_STENCIL_TEST)
416                         glStencilFunc(GL_EQUAL, 0, 255)
417                         glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
418                         glLineWidth(2)
419                         glColor4f(1,1,1,0.5)
420                         self._renderObject(self._selectedObj)
421                         glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
422                         glDisable(GL_STENCIL_TEST)
423                         glDisable(GL_CULL_FACE)
424                         glEnable(GL_DEPTH_TEST)
425
426         def _renderObject(self, obj):
427                 glPushMatrix()
428                 glTranslate(obj.getPosition()[0], obj.getPosition()[1], 0)
429                 offset = obj.getDrawOffset()
430                 glTranslate(-offset[0], -offset[1], -offset[2])
431                 for m in obj._meshList:
432                         if m.vbo is None:
433                                 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
434                         m.vbo.render()
435                 glPopMatrix()
436
437         def _drawMachine(self):
438                 glEnable(GL_CULL_FACE)
439                 glEnable(GL_BLEND)
440
441                 if profile.getPreference('machine_type') == 'ultimaker':
442                         glColor4f(1,1,1,0.5)
443                         self._objectShader.bind()
444                         self._objectShader.setUniform('cameraDistance', self._zoom)
445                         self._renderObject(self._platformMesh)
446                         self._objectShader.unbind()
447
448                 size = [profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')]
449                 v0 = [ size[0] / 2, size[1] / 2, size[2]]
450                 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
451                 v2 = [-size[0] / 2, size[1] / 2, size[2]]
452                 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
453                 v4 = [ size[0] / 2, size[1] / 2, 0]
454                 v5 = [ size[0] / 2,-size[1] / 2, 0]
455                 v6 = [-size[0] / 2, size[1] / 2, 0]
456                 v7 = [-size[0] / 2,-size[1] / 2, 0]
457
458                 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
459                 glEnableClientState(GL_VERTEX_ARRAY)
460                 glVertexPointer(3, GL_FLOAT, 3*4, vList)
461
462                 glColor4ub(5, 171, 231, 64)
463                 glDrawArrays(GL_QUADS, 0, 4)
464                 glColor4ub(5, 171, 231, 96)
465                 glDrawArrays(GL_QUADS, 4, 8)
466                 glColor4ub(5, 171, 231, 128)
467                 glDrawArrays(GL_QUADS, 12, 8)
468
469                 sx = self._machineSize[0]
470                 sy = self._machineSize[1]
471                 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
472                         for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
473                                 x1 = x * 10
474                                 x2 = x1 + 10
475                                 y1 = y * 10
476                                 y2 = y1 + 10
477                                 x1 = max(min(x1, sx/2), -sx/2)
478                                 y1 = max(min(y1, sy/2), -sy/2)
479                                 x2 = max(min(x2, sx/2), -sx/2)
480                                 y2 = max(min(y2, sy/2), -sy/2)
481                                 if (x & 1) == (y & 1):
482                                         glColor4ub(5, 171, 231, 127)
483                                 else:
484                                         glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
485                                 glBegin(GL_QUADS)
486                                 glVertex3f(x1, y1, -0.02)
487                                 glVertex3f(x2, y1, -0.02)
488                                 glVertex3f(x2, y2, -0.02)
489                                 glVertex3f(x1, y2, -0.02)
490                                 glEnd()
491
492                 glDisableClientState(GL_VERTEX_ARRAY)
493                 glDisable(GL_BLEND)
494                 glDisable(GL_CULL_FACE)
495
496 class shaderEditor(wx.Dialog):
497         def __init__(self, parent, callback, v, f):
498                 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
499                 self._callback = callback
500                 s = wx.BoxSizer(wx.VERTICAL)
501                 self.SetSizer(s)
502                 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
503                 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
504                 s.Add(self._vertex, 1, flag=wx.EXPAND)
505                 s.Add(self._fragment, 1, flag=wx.EXPAND)
506
507                 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
508                 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
509
510                 self.SetPosition(self.GetParent().GetPosition())
511                 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
512                 self.Show()
513
514         def OnText(self, e):
515                 self._callback(self._vertex.GetValue(), self._fragment.GetValue())