1 from __future__ import absolute_import
11 OpenGL.ERROR_CHECKING = False
12 from OpenGL.GLU import *
13 from OpenGL.GL import *
15 from Cura.gui import printWindow
16 from Cura.util import profile
17 from Cura.util import meshLoader
18 from Cura.util import objectScene
19 from Cura.util import resources
20 from Cura.util import sliceEngine
21 from Cura.util import machineCom
22 from Cura.util import removableStorage
23 from Cura.gui.util import opengl
24 from Cura.gui.util import openglGui
27 def __init__(self, start, end, runTime):
30 self._startTime = time.time()
31 self._runTime = runTime
34 return time.time() > self._startTime + self._runTime
36 def getPosition(self):
39 f = (time.time() - self._startTime) / self._runTime
42 #f = 6*tc*ts + -15*ts*ts + 10*tc
44 return self._start + (self._end - self._start) * f
46 class SceneView(openglGui.glGuiPanel):
47 def __init__(self, parent):
48 super(SceneView, self).__init__(parent)
53 self._scene = objectScene.Scene()
54 self._objectShader = None
56 self._selectedObj = None
57 self._objColors = [None,None,None,None]
60 self._mouseState = None
61 self._viewTarget = numpy.array([0,0,0], numpy.float32)
64 self._platformMesh = meshLoader.loadMeshes(resources.getPathForMesh('ultimaker_platform.stl'))[0]
65 self._platformMesh._drawOffset = numpy.array([0,0,0.5], numpy.float32)
66 self._isSimpleMode = True
68 self.openFileButton = openglGui.glButton(self, 4, 'Load', (0,0), self.ShowLoadModel)
69 self.printButton = openglGui.glButton(self, 6, 'Print', (1,0), self.ShowPrintWindow)
70 self.printButton.setDisabled(True)
72 self._slicer = sliceEngine.Slicer(self._updateSliceProgress)
73 self._sceneUpdateTimer = wx.Timer(self)
74 self.Bind(wx.EVT_TIMER, lambda e : self._slicer.runSlicer(self._scene), self._sceneUpdateTimer)
75 self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
77 self.updateProfileToControls()
78 wx.EVT_IDLE(self, self.OnIdle)
80 def ShowLoadModel(self, button):
82 dlg=wx.FileDialog(self, 'Open 3D model', os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
83 dlg.SetWildcard(meshLoader.wildcardFilter())
84 if dlg.ShowModal() != wx.ID_OK:
87 filename = dlg.GetPath()
89 if not(os.path.exists(filename)):
91 profile.putPreference('lastFile', filename)
92 self.GetParent().GetParent().GetParent().addToModelMRU(filename)
93 self.loadScene([filename])
95 def ShowPrintWindow(self, button):
97 if machineCom.machineIsConnected():
98 printWindow.printFile(self._slicer.getGCodeFilename())
99 elif len(removableStorage.getPossibleSDcardDrives()) > 0:
100 drives = removableStorage.getPossibleSDcardDrives()
104 defPath = profile.getPreference('lastFile')
105 defPath = defPath[0:defPath.rfind('.')] + '.gcode'
106 dlg=wx.FileDialog(self, 'Save toolpath', defPath, style=wx.FD_SAVE)
107 dlg.SetFilename(defPath)
108 dlg.SetWildcard('Toolpath (*.gcode)|*.gcode;*.g')
109 if dlg.ShowModal() != wx.ID_OK:
112 filename = dlg.GetPath()
115 shutil.copy(self._slicer.getGCodeFilename(), filename)
118 if self._animView is not None or self._animZoom is not None:
121 for obj in self._scene.objects():
122 if obj._loadAnim is not None:
126 def sceneUpdated(self):
127 self._sceneUpdateTimer.Start(1, True)
128 self._slicer.abortSlicer()
129 self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
132 def _updateSliceProgress(self, progressValue, ready):
133 self.printButton.setDisabled(not ready)
134 self.printButton.setProgressBar(progressValue)
137 def loadScene(self, fileList):
138 for filename in fileList:
140 objList = meshLoader.loadMeshes(filename)
142 traceback.print_exc()
145 obj._loadAnim = anim(1, 0, 1.5)
147 self._scene.centerAll()
148 self._selectObject(obj)
151 def _deleteObject(self, obj):
152 if obj == self._selectedObj:
153 self._selectedObj = None
154 if obj == self._focusObj:
155 self._focusObj = None
156 self._scene.remove(obj)
157 for m in obj._meshList:
158 if m.vbo is not None:
159 self.glReleaseList.append(m.vbo)
160 if self._isSimpleMode:
161 self._scene.arrangeAll()
164 def _selectObject(self, obj, zoom = True):
165 if obj != self._selectedObj:
166 self._selectedObj = obj
168 newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getMaximum()[2] / 2])
169 self._animView = anim(self._viewTarget.copy(), newViewPos, 0.5)
170 newZoom = obj.getBoundaryCircle() * 6
171 if newZoom > numpy.max(self._machineSize) * 3:
172 newZoom = numpy.max(self._machineSize) * 3
173 self._animZoom = anim(self._zoom, newZoom, 0.5)
175 def updateProfileToControls(self):
176 oldSimpleMode = self._isSimpleMode
177 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
178 if self._isSimpleMode and not oldSimpleMode:
179 self._scene.arrangeAll()
181 self._machineSize = numpy.array([profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')])
182 self._objColors[0] = profile.getPreferenceColour('model_colour')
183 self._objColors[1] = profile.getPreferenceColour('model_colour2')
184 self._objColors[2] = profile.getPreferenceColour('model_colour3')
185 self._objColors[3] = profile.getPreferenceColour('model_colour4')
186 self._scene.setMachineSize(self._machineSize)
188 def OnKeyChar(self, keyCode):
189 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE:
190 if self._selectedObj is not None:
191 self._deleteObject(self._selectedObj)
194 if keyCode == wx.WXK_F3:
195 shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
197 def ShaderUpdate(self, v, f):
198 s = opengl.GLShader(v, f)
200 self._objectLoadShader.release()
201 self._objectLoadShader = s
202 for obj in self._scene.objects():
203 obj._loadAnim = anim(1, 0, 1.5)
206 def OnMouseDown(self,e):
207 self._mouseX = e.GetX()
208 self._mouseY = e.GetY()
209 self._mouseClick3DPos = self._mouse3Dpos
210 self._mouseClickFocus = self._focusObj
212 self._mouseState = 'doubleClick'
214 self._mouseState = 'dragOrClick'
215 if self._mouseState == 'dragOrClick':
217 if self._focusObj is not None:
218 self._selectObject(self._focusObj, False)
221 def OnMouseUp(self, e):
222 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
224 if self._mouseState == 'dragOrClick':
226 if self._focusObj is not None:
227 self._selectObject(self._focusObj)
229 self._selectedObj = None
231 if e.Button == 3 and self._selectedObj == self._focusObj:
233 #menu.Append(-1, 'Test')
234 #self.PopupMenu(menu)
237 if self._mouseState == 'dragObject' and self._selectedObj is not None:
238 self._scene.pushFree()
240 self._mouseState = None
242 def OnMouseMotion(self,e):
243 if e.Dragging() and self._mouseState is not None:
244 self._mouseState = 'drag'
245 if not e.LeftIsDown() and e.RightIsDown():
246 self._yaw += e.GetX() - self._mouseX
247 self._pitch -= e.GetY() - self._mouseY
248 if self._pitch > 170:
252 elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
253 self._zoom += e.GetY() - self._mouseY
256 if self._zoom > numpy.max(self._machineSize) * 3:
257 self._zoom = numpy.max(self._machineSize) * 3
258 elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus and not self._isSimpleMode:
259 self._mouseState = 'dragObject'
260 z = max(0, self._mouseClick3DPos[2])
261 p0 = opengl.unproject(self._mouseX, self.viewport[1] + self.viewport[3] - self._mouseY, 0, self.modelMatrix, self.projMatrix, self.viewport)
262 p1 = opengl.unproject(self._mouseX, self.viewport[1] + self.viewport[3] - self._mouseY, 1, self.modelMatrix, self.projMatrix, self.viewport)
263 p2 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport)
264 p3 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport)
269 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
270 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
271 diff = cursorZ1 - cursorZ0
272 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
274 self._mouseX = e.GetX()
275 self._mouseY = e.GetY()
277 def OnMouseWheel(self, e):
278 self._zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
281 if self._zoom > numpy.max(self._machineSize) * 3:
282 self._zoom = numpy.max(self._machineSize) * 3
285 def _init3DView(self):
286 # set viewing projection
287 size = self.GetSize()
288 glViewport(0, 0, size.GetWidth(), size.GetHeight())
291 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
293 glDisable(GL_RESCALE_NORMAL)
294 glDisable(GL_LIGHTING)
296 glEnable(GL_DEPTH_TEST)
297 glDisable(GL_CULL_FACE)
299 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
301 glClearColor(0.8, 0.8, 0.8, 1.0)
305 glMatrixMode(GL_PROJECTION)
307 aspect = float(size.GetWidth()) / float(size.GetHeight())
308 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
310 glMatrixMode(GL_MODELVIEW)
312 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
315 if machineCom.machineIsConnected():
316 self.printButton._imageID = 6
317 self.printButton._tooltip = 'Print'
318 elif len(removableStorage.getPossibleSDcardDrives()) > 0:
319 self.printButton._imageID = 2
320 self.printButton._tooltip = 'Toolpath to SD'
322 self.printButton._imageID = 3
323 self.printButton._tooltip = 'Save toolpath'
325 if self._animView is not None:
326 self._viewTarget = self._animView.getPosition()
327 if self._animView.isDone():
328 self._animView = None
329 if self._animZoom is not None:
330 self._zoom = self._animZoom.getPosition()
331 if self._animZoom.isDone():
332 self._animZoom = None
333 if self._objectShader is None:
334 self._objectShader = opengl.GLShader("""
335 uniform float cameraDistance;
336 varying float light_amount;
340 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
341 gl_FrontColor = gl_Color;
343 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
344 light_amount *= 1.0 - (length(gl_Position.xyz - vec3(0.0,0.0,cameraDistance)) / 1.5 / cameraDistance);
348 uniform float cameraDistance;
349 varying float light_amount;
353 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
356 self._objectLoadShader = opengl.GLShader("""
357 uniform float cameraDistance;
358 uniform float intensity;
360 varying float light_amount;
364 vec4 tmp = gl_Vertex;
365 tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
366 tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
367 gl_Position = gl_ModelViewProjectionMatrix * tmp;
368 gl_FrontColor = gl_Color;
370 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
371 light_amount *= 1.0 - (length(gl_Position.xyz - vec3(0.0,0.0,cameraDistance)) / 1.5 / cameraDistance);
375 uniform float cameraDistance;
376 uniform float intensity;
377 varying float light_amount;
381 gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
385 glTranslate(0,0,-self._zoom)
386 glRotate(-self._pitch, 1,0,0)
387 glRotate(self._yaw, 0,0,1)
388 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
390 self.viewport = glGetIntegerv(GL_VIEWPORT)
391 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
392 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
394 glClearColor(1,1,1,1)
395 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
397 for n in xrange(0, len(self._scene.objects())):
398 obj = self._scene.objects()[n]
399 glColor4ub((n >> 24) & 0xFF, (n >> 16) & 0xFF, (n >> 8) & 0xFF, n & 0xFF)
400 self._renderObject(obj)
402 if self._mouseX > -1:
403 n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0]
404 if n < len(self._scene.objects()):
405 self._focusObj = self._scene.objects()[n]
407 self._focusObj = None
408 f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
409 self._mouse3Dpos = opengl.unproject(self._mouseX, self.viewport[1] + self.viewport[3] - self._mouseY, f, self.modelMatrix, self.projMatrix, self.viewport)
412 glTranslate(0,0,-self._zoom)
413 glRotate(-self._pitch, 1,0,0)
414 glRotate(self._yaw, 0,0,1)
415 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
417 glStencilFunc(GL_ALWAYS, 1, 1)
418 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
419 self._objectShader.bind()
420 self._objectShader.setUniform('cameraDistance', self._zoom)
421 for obj in self._scene.objects():
422 if obj._loadAnim is not None:
423 if obj._loadAnim.isDone():
427 col = self._objColors[0]
428 if not self._scene.checkPlatform(obj):
429 col = [0.5,0.5,0.5,0.8]
430 glDisable(GL_STENCIL_TEST)
431 if self._selectedObj == obj:
432 col = map(lambda n: n * 1.5, col)
433 glEnable(GL_STENCIL_TEST)
434 elif self._focusObj == obj:
435 col = map(lambda n: n * 1.2, col)
436 elif self._focusObj is not None or self._selectedObj is not None:
437 col = map(lambda n: n * 0.8, col)
438 glColor4f(col[0], col[1], col[2], col[3])
439 self._renderObject(obj)
440 self._objectShader.unbind()
442 glDisable(GL_STENCIL_TEST)
444 self._objectLoadShader.bind()
445 self._objectLoadShader.setUniform('cameraDistance', self._zoom)
446 glColor4f(0.2, 0.6, 1.0, 1.0)
447 for obj in self._scene.objects():
448 if obj._loadAnim is None:
450 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
451 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
452 self._renderObject(obj)
453 self._objectLoadShader.unbind()
458 #Draw the outline of the selected object, on top of everything else except the GUI.
459 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
460 glDisable(GL_DEPTH_TEST)
461 glEnable(GL_CULL_FACE)
462 glEnable(GL_STENCIL_TEST)
463 glStencilFunc(GL_EQUAL, 0, 255)
464 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
467 self._renderObject(self._selectedObj)
468 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
469 glDisable(GL_STENCIL_TEST)
470 glDisable(GL_CULL_FACE)
471 glEnable(GL_DEPTH_TEST)
473 def _renderObject(self, obj):
475 glTranslate(obj.getPosition()[0], obj.getPosition()[1], 0)
476 offset = obj.getDrawOffset()
477 glTranslate(-offset[0], -offset[1], -offset[2])
478 for m in obj._meshList:
480 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
484 def _drawMachine(self):
485 glEnable(GL_CULL_FACE)
488 if profile.getPreference('machine_type') == 'ultimaker':
490 self._objectShader.bind()
491 self._objectShader.setUniform('cameraDistance', self._zoom)
492 self._renderObject(self._platformMesh)
493 self._objectShader.unbind()
495 size = [profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')]
496 v0 = [ size[0] / 2, size[1] / 2, size[2]]
497 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
498 v2 = [-size[0] / 2, size[1] / 2, size[2]]
499 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
500 v4 = [ size[0] / 2, size[1] / 2, 0]
501 v5 = [ size[0] / 2,-size[1] / 2, 0]
502 v6 = [-size[0] / 2, size[1] / 2, 0]
503 v7 = [-size[0] / 2,-size[1] / 2, 0]
505 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
506 glEnableClientState(GL_VERTEX_ARRAY)
507 glVertexPointer(3, GL_FLOAT, 3*4, vList)
509 glColor4ub(5, 171, 231, 64)
510 glDrawArrays(GL_QUADS, 0, 4)
511 glColor4ub(5, 171, 231, 96)
512 glDrawArrays(GL_QUADS, 4, 8)
513 glColor4ub(5, 171, 231, 128)
514 glDrawArrays(GL_QUADS, 12, 8)
516 sx = self._machineSize[0]
517 sy = self._machineSize[1]
518 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
519 for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
524 x1 = max(min(x1, sx/2), -sx/2)
525 y1 = max(min(y1, sy/2), -sy/2)
526 x2 = max(min(x2, sx/2), -sx/2)
527 y2 = max(min(y2, sy/2), -sy/2)
528 if (x & 1) == (y & 1):
529 glColor4ub(5, 171, 231, 127)
531 glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
533 glVertex3f(x1, y1, -0.02)
534 glVertex3f(x2, y1, -0.02)
535 glVertex3f(x2, y2, -0.02)
536 glVertex3f(x1, y2, -0.02)
539 glDisableClientState(GL_VERTEX_ARRAY)
541 glDisable(GL_CULL_FACE)
543 class shaderEditor(wx.Dialog):
544 def __init__(self, parent, callback, v, f):
545 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
546 self._callback = callback
547 s = wx.BoxSizer(wx.VERTICAL)
549 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
550 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
551 s.Add(self._vertex, 1, flag=wx.EXPAND)
552 s.Add(self._fragment, 1, flag=wx.EXPAND)
554 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
555 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
557 self.SetPosition(self.GetParent().GetPosition())
558 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
562 self._callback(self._vertex.GetValue(), self._fragment.GetValue())