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