1 from __future__ import absolute_import
9 OpenGL.ERROR_CHECKING = False
10 from OpenGL.GLU import *
11 from OpenGL.GL import *
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
24 def __init__(self, start, end, runTime):
27 self._startTime = time.time()
28 self._runTime = runTime
31 return time.time() > self._startTime + self._runTime
33 def getPosition(self):
36 f = (time.time() - self._startTime) / self._runTime
39 #f = 6*tc*ts + -15*ts*ts + 10*tc
41 return self._start + (self._end - self._start) * f
43 class SceneView(openglGui.glGuiPanel):
44 def __init__(self, parent):
45 super(SceneView, self).__init__(parent)
50 self._scene = objectScene.Scene()
51 self._objectShader = None
53 self._selectedObj = None
54 self._objColors = [None,None,None,None]
57 self._mouseState = None
58 self._viewTarget = numpy.array([0,0,0], numpy.float32)
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
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)
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)
73 self.updateProfileToControls()
74 wx.EVT_IDLE(self, self.OnIdle)
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:
82 filename = dlg.GetPath()
84 if not(os.path.exists(filename)):
86 profile.putPreference('lastFile', filename)
87 self.GetParent().GetParent().GetParent().addToModelMRU(filename)
88 self.loadScene([filename])
90 def ShowPrintWindow(self):
91 if machineCom.machineIsConnected():
92 printWindow.printFile(self._slicer.getGCodeFilename())
95 if self._animView is not None or self._animZoom is not None:
98 for obj in self._scene.objects():
99 if obj._loadAnim is not None:
103 def sceneUpdated(self):
104 self._sceneUpdateTimer.Start(1, True)
105 self._slicer.abortSlicer()
108 def _updateSliceProgress(self, progressValue, ready):
109 self.printButton.setDisabled(not ready)
110 self.printButton.setProgressBar(progressValue)
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)
118 self._selectObject(obj)
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()
134 def _selectObject(self, obj, zoom = True):
135 if obj != self._selectedObj:
136 self._selectedObj = obj
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)
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()
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)
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)
164 if keyCode == wx.WXK_F3:
165 shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
167 def ShaderUpdate(self, v, f):
168 s = opengl.GLShader(v, f)
170 self._objectLoadShader.release()
171 self._objectLoadShader = s
172 for obj in self._scene.objects():
173 obj._loadAnim = anim(1, 0, 1.5)
176 def OnMouseDown(self,e):
177 self._mouseX = e.GetX()
178 self._mouseY = e.GetY()
179 self._mouseClick3DPos = self._mouse3Dpos
181 self._mouseState = 'doubleClick'
183 self._mouseState = 'dragOrClick'
184 if self._mouseState == 'dragOrClick':
186 if self._focusObj is not None:
187 self._selectObject(self._focusObj, False)
190 def OnMouseUp(self, e):
191 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
193 if self._mouseState == 'dragOrClick':
195 if self._focusObj is not None:
196 self._selectObject(self._focusObj)
198 self._selectedObj = None
200 if e.Button == 3 and self._selectedObj == self._focusObj:
202 #menu.Append(-1, 'Test')
203 #self.PopupMenu(menu)
206 if self._mouseState == 'dragObject' and self._selectedObj is not None:
207 self._scene.pushFree()
209 self._mouseState = None
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:
221 elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
222 self._zoom += e.GetY() - self._mouseY
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)
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])
243 self._mouseX = e.GetX()
244 self._mouseY = e.GetY()
246 def _init3DView(self):
247 # set viewing projection
248 size = self.GetSize()
249 glViewport(0, 0, size.GetWidth(), size.GetHeight())
252 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
254 glDisable(GL_RESCALE_NORMAL)
255 glDisable(GL_LIGHTING)
257 glEnable(GL_DEPTH_TEST)
258 glDisable(GL_CULL_FACE)
260 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
262 glClearColor(0.8, 0.8, 0.8, 1.0)
266 glMatrixMode(GL_PROJECTION)
268 aspect = float(size.GetWidth()) / float(size.GetHeight())
269 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
271 glMatrixMode(GL_MODELVIEW)
273 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
276 self.printButton._imageID = 6 if machineCom.machineIsConnected() else 2
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;
293 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
294 gl_FrontColor = gl_Color;
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);
301 uniform float cameraDistance;
302 varying float light_amount;
306 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
309 self._objectLoadShader = opengl.GLShader("""
310 uniform float cameraDistance;
311 uniform float intensity;
313 varying float light_amount;
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;
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);
328 uniform float cameraDistance;
329 uniform float intensity;
330 varying float light_amount;
334 gl_FragColor = vec4(gl_Color.xyz * light_amount, 1-intensity);
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])
343 self.viewport = glGetIntegerv(GL_VIEWPORT)
344 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
345 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
347 glClearColor(1,1,1,1)
348 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
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)
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]
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)
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])
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():
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()
395 glDisable(GL_STENCIL_TEST)
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:
403 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
404 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
405 self._renderObject(obj)
406 self._objectLoadShader.unbind()
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)
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)
426 def _renderObject(self, obj):
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:
433 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
437 def _drawMachine(self):
438 glEnable(GL_CULL_FACE)
441 if profile.getPreference('machine_type') == 'ultimaker':
443 self._objectShader.bind()
444 self._objectShader.setUniform('cameraDistance', self._zoom)
445 self._renderObject(self._platformMesh)
446 self._objectShader.unbind()
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]
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)
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)
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):
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)
484 glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
486 glVertex3f(x1, y1, -0.02)
487 glVertex3f(x2, y1, -0.02)
488 glVertex3f(x2, y2, -0.02)
489 glVertex3f(x1, y2, -0.02)
492 glDisableClientState(GL_VERTEX_ARRAY)
494 glDisable(GL_CULL_FACE)
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)
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)
507 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
508 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
510 self.SetPosition(self.GetParent().GetPosition())
511 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
515 self._callback(self._vertex.GetValue(), self._fragment.GetValue())