2 from __future__ import absolute_import
9 from Cura.util import meshLoader
10 from Cura.util import util3d
11 from Cura.util import profile
12 from Cura.util.resources import getPathForMesh, getPathForImage
16 OpenGL.ERROR_CHECKING = False
17 from OpenGL.GLUT import *
18 from OpenGL.GLU import *
19 from OpenGL.GL import *
20 from OpenGL.GL import shaders
25 class GLReferenceCounter(object):
34 return self._refCounter <= 0
36 class GLShader(GLReferenceCounter):
37 def __init__(self, vertexProgram, fragmentProgram):
38 super(GLShader, self).__init__()
39 self._vertexString = vertexProgram
40 self._fragmentString = fragmentProgram
42 self._vertexProgram = shaders.compileShader(vertexProgram, GL_VERTEX_SHADER)
43 self._fragmentProgram = shaders.compileShader(fragmentProgram, GL_FRAGMENT_SHADER)
44 self._program = shaders.compileProgram(self._vertexProgram, self._fragmentProgram)
45 except RuntimeError, e:
50 if self._program is not None:
51 shaders.glUseProgram(self._program)
54 shaders.glUseProgram(0)
57 if self._program is not None:
58 shaders.glDeleteShader(self._vertexProgram)
59 shaders.glDeleteShader(self._fragmentProgram)
60 glDeleteProgram(self._program)
63 def setUniform(self, name, value):
64 if self._program is not None:
65 glUniform1f(glGetUniformLocation(self._program, name), value)
68 return self._program is not None
70 def getVertexShader(self):
71 return self._vertexString
73 def getFragmentShader(self):
74 return self._fragmentString
77 if self._program is not None and bool(glDeleteProgram):
78 print "Shader was not properly released!"
80 class GLVBO(GLReferenceCounter):
81 def __init__(self, vertexArray, normalArray = None):
82 super(GLVBO, self).__init__()
83 self._buffer = glGenBuffers(1)
84 self._size = len(vertexArray)
85 self._hasNormals = normalArray is not None
86 glBindBuffer(GL_ARRAY_BUFFER, self._buffer)
88 glBufferData(GL_ARRAY_BUFFER, numpy.concatenate((vertexArray, normalArray), 1), GL_STATIC_DRAW)
90 glBufferData(GL_ARRAY_BUFFER, vertexArray, GL_STATIC_DRAW)
91 glBindBuffer(GL_ARRAY_BUFFER, 0)
93 def render(self, render_type = GL_TRIANGLES):
94 glEnableClientState(GL_VERTEX_ARRAY)
95 glBindBuffer(GL_ARRAY_BUFFER, self._buffer)
98 glEnableClientState(GL_NORMAL_ARRAY)
99 glVertexPointer(3, GL_FLOAT, 2*3*4, c_void_p(0))
100 glNormalPointer(GL_FLOAT, 2*3*4, c_void_p(3 * 4))
102 glVertexPointer(3, GL_FLOAT, 3*4, c_void_p(0))
104 batchSize = 996 #Warning, batchSize needs to be dividable by 4, 3 and 2
105 extraStartPos = int(self._size / batchSize) * batchSize
106 extraCount = self._size - extraStartPos
108 for i in xrange(0, int(self._size / batchSize)):
109 glDrawArrays(render_type, i * batchSize, batchSize)
110 glDrawArrays(render_type, extraStartPos, extraCount)
111 glBindBuffer(GL_ARRAY_BUFFER, 0)
113 glDisableClientState(GL_VERTEX_ARRAY)
115 glDisableClientState(GL_NORMAL_ARRAY)
118 if self._buffer is not None:
119 glDeleteBuffers(1, [self._buffer])
123 if self._buffer is not None and bool(glDeleteBuffers):
124 print "VBO was not properly released!"
126 def DrawMachine(machineSize):
127 glDisable(GL_LIGHTING)
128 glDisable(GL_CULL_FACE)
130 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
134 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
135 for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
140 x1 = max(min(x1, sx), 0)
141 y1 = max(min(y1, sy), 0)
142 x2 = max(min(x2, sx), 0)
143 y2 = max(min(y2, sy), 0)
144 if (x & 1) == (y & 1):
145 glColor4ub(5, 171, 231, 127)
147 glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
149 glVertex3f(x1, y1, -0.02)
150 glVertex3f(x2, y1, -0.02)
151 glVertex3f(x2, y2, -0.02)
152 glVertex3f(x1, y2, -0.02)
155 glEnable(GL_CULL_FACE)
157 if profile.getPreference('machine_type') == 'ultimaker':
159 glEnable(GL_LIGHTING)
160 glTranslate(100, 200, -1)
161 glLightfv(GL_LIGHT0, GL_DIFFUSE, [0.8, 0.8, 0.8])
162 glLightfv(GL_LIGHT0, GL_AMBIENT, [0.5, 0.5, 0.5])
164 glBlendFunc(GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR)
167 if platformMesh is None:
169 platformMesh = meshLoader.loadMesh(getPathForMesh('ultimaker_platform.stl'))
174 DrawMesh(platformMesh)
176 glDisable(GL_LIGHTING)
177 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
179 glColor4ub(5, 171, 231, 64)
181 glVertex3f(0, 0, machineSize.z)
182 glVertex3f(0, machineSize.y, machineSize.z)
183 glVertex3f(machineSize.x, machineSize.y, machineSize.z)
184 glVertex3f(machineSize.x, 0, machineSize.z)
187 glColor4ub(5, 171, 231, 96)
190 glVertex3f(0, 0, machineSize.z)
191 glVertex3f(machineSize.x, 0, machineSize.z)
192 glVertex3f(machineSize.x, 0, 0)
194 glVertex3f(0, machineSize.y, machineSize.z)
195 glVertex3f(0, machineSize.y, 0)
196 glVertex3f(machineSize.x, machineSize.y, 0)
197 glVertex3f(machineSize.x, machineSize.y, machineSize.z)
200 glColor4ub(5, 171, 231, 128)
202 glVertex3f(0, 0, machineSize.z)
204 glVertex3f(0, machineSize.y, 0)
205 glVertex3f(0, machineSize.y, machineSize.z)
207 glVertex3f(machineSize.x, 0, 0)
208 glVertex3f(machineSize.x, 0, machineSize.z)
209 glVertex3f(machineSize.x, machineSize.y, machineSize.z)
210 glVertex3f(machineSize.x, machineSize.y, 0)
215 #Draw the X/Y/Z indicator
235 glDisable(GL_DEPTH_TEST)
239 glTranslate(20, 0, 0)
240 noZ = ResetMatrixRotationAndScale()
241 glDrawStringCenter("X")
247 glTranslate(0, 20, 0)
248 glDrawStringCenter("Y")
255 glTranslate(0, 0, 20)
256 glDrawStringCenter("Z")
260 glEnable(GL_DEPTH_TEST)
262 def glDrawStringCenter(s):
264 glBitmap(0,0,0,0, -glGetStringSize(s)[0]/2, 0, None)
266 glutBitmapCharacter(OpenGL.GLUT.GLUT_BITMAP_HELVETICA_18, ord(c))
268 def glGetStringSize(s):
271 width += glutBitmapWidth(OpenGL.GLUT.GLUT_BITMAP_HELVETICA_18, ord(c))
275 def glDrawStringLeft(s):
281 glTranslate(0, 18 * n, 0)
286 glutBitmapCharacter(OpenGL.GLUT.GLUT_BITMAP_HELVETICA_18, ord(c))
288 def glDrawStringRight(s):
290 glBitmap(0,0,0,0, -glGetStringSize(s)[0], 0, None)
292 glutBitmapCharacter(OpenGL.GLUT.GLUT_BITMAP_HELVETICA_18, ord(c))
294 def glDrawTexturedQuad(x, y, w, h, texID, mirror = 0):
295 tx = float(texID % 4) / 4
296 ty = float(int(texID / 4)) / 8
306 glTranslatef(x, y, 0)
307 glEnable(GL_TEXTURE_2D)
309 glTexCoord2f(tx+tsx, ty)
313 glTexCoord2f(tx, ty+tsy)
315 glTexCoord2f(tx+tsx, ty+tsy)
320 def glDrawStretchedQuad(x, y, w, h, cornerSize, texID):
321 tx0 = float(texID % 4) / 4
322 ty0 = float(int(texID / 4)) / 8
323 tx1 = tx0 + 0.25 / 2.0
324 ty1 = ty0 + 0.125 / 2.0
329 glTranslatef(x, y, 0)
330 glEnable(GL_TEXTURE_2D)
333 glTexCoord2f(tx1, ty0)
334 glVertex2f( cornerSize, 0)
335 glTexCoord2f(tx0, ty0)
337 glTexCoord2f(tx0, ty1)
338 glVertex2f( 0, cornerSize)
339 glTexCoord2f(tx1, ty1)
340 glVertex2f( cornerSize, cornerSize)
342 glTexCoord2f(tx2, ty0)
344 glTexCoord2f(tx1, ty0)
345 glVertex2f( w - cornerSize, 0)
346 glTexCoord2f(tx1, ty1)
347 glVertex2f( w - cornerSize, cornerSize)
348 glTexCoord2f(tx2, ty1)
349 glVertex2f( w, cornerSize)
351 glTexCoord2f(tx1, ty1)
352 glVertex2f( cornerSize, h - cornerSize)
353 glTexCoord2f(tx0, ty1)
354 glVertex2f( 0, h - cornerSize)
355 glTexCoord2f(tx0, ty2)
357 glTexCoord2f(tx1, ty2)
358 glVertex2f( cornerSize, h)
360 glTexCoord2f(tx2, ty1)
361 glVertex2f( w, h - cornerSize)
362 glTexCoord2f(tx1, ty1)
363 glVertex2f( w - cornerSize, h - cornerSize)
364 glTexCoord2f(tx1, ty2)
365 glVertex2f( w - cornerSize, h)
366 glTexCoord2f(tx2, ty2)
370 glTexCoord2f(tx1, ty1)
371 glVertex2f( w-cornerSize, cornerSize)
372 glTexCoord2f(tx1, ty1)
373 glVertex2f( cornerSize, cornerSize)
374 glTexCoord2f(tx1, ty1)
375 glVertex2f( cornerSize, h-cornerSize)
376 glTexCoord2f(tx1, ty1)
377 glVertex2f( w-cornerSize, h-cornerSize)
380 glTexCoord2f(tx2, ty1)
381 glVertex2f( w, cornerSize)
382 glTexCoord2f(tx1, ty1)
383 glVertex2f( w-cornerSize, cornerSize)
384 glTexCoord2f(tx1, ty1)
385 glVertex2f( w-cornerSize, h-cornerSize)
386 glTexCoord2f(tx2, ty1)
387 glVertex2f( w, h-cornerSize)
390 glTexCoord2f(tx1, ty1)
391 glVertex2f( cornerSize, cornerSize)
392 glTexCoord2f(tx0, ty1)
393 glVertex2f( 0, cornerSize)
394 glTexCoord2f(tx0, ty1)
395 glVertex2f( 0, h-cornerSize)
396 glTexCoord2f(tx1, ty1)
397 glVertex2f( cornerSize, h-cornerSize)
400 glTexCoord2f(tx1, ty0)
401 glVertex2f( w-cornerSize, 0)
402 glTexCoord2f(tx1, ty0)
403 glVertex2f( cornerSize, 0)
404 glTexCoord2f(tx1, ty1)
405 glVertex2f( cornerSize, cornerSize)
406 glTexCoord2f(tx1, ty1)
407 glVertex2f( w-cornerSize, cornerSize)
410 glTexCoord2f(tx1, ty1)
411 glVertex2f( w-cornerSize, h-cornerSize)
412 glTexCoord2f(tx1, ty1)
413 glVertex2f( cornerSize, h-cornerSize)
414 glTexCoord2f(tx1, ty2)
415 glVertex2f( cornerSize, h)
416 glTexCoord2f(tx1, ty2)
417 glVertex2f( w-cornerSize, h)
420 glDisable(GL_TEXTURE_2D)
423 def unproject(winx, winy, winz, modelMatrix, projMatrix, viewport):
424 npModelMatrix = numpy.matrix(numpy.array(modelMatrix, numpy.float64).reshape((4,4)))
425 npProjMatrix = numpy.matrix(numpy.array(projMatrix, numpy.float64).reshape((4,4)))
426 finalMatrix = npModelMatrix * npProjMatrix
427 finalMatrix = numpy.linalg.inv(finalMatrix)
429 viewport = map(float, viewport)
430 vector = numpy.array([(winx - viewport[0]) / viewport[2] * 2.0 - 1.0, (winy - viewport[1]) / viewport[3] * 2.0 - 1.0, winz * 2.0 - 1.0, 1]).reshape((1,4))
431 vector = (numpy.matrix(vector) * finalMatrix).getA().flatten()
432 ret = list(vector)[0:3] / vector[3]
435 def convert3x3MatrixTo4x4(matrix):
436 return list(matrix.getA()[0]) + [0] + list(matrix.getA()[1]) + [0] + list(matrix.getA()[2]) + [0, 0,0,0,1]
438 def loadGLTexture(filename):
439 tex = glGenTextures(1)
440 glBindTexture(GL_TEXTURE_2D, tex)
441 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
442 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
443 img = wx.ImageFromBitmap(wx.Bitmap(getPathForImage(filename)))
444 rgbData = img.GetData()
445 alphaData = img.GetAlphaData()
446 if alphaData is not None:
448 for i in xrange(0, len(alphaData)):
449 data += rgbData[i*3:i*3+3] + alphaData[i]
450 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, img.GetWidth(), img.GetHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, data)
452 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, img.GetWidth(), img.GetHeight(), 0, GL_RGB, GL_UNSIGNED_BYTE, rgbData)
455 def ResetMatrixRotationAndScale():
456 matrix = glGetFloatv(GL_MODELVIEW_MATRIX)
460 scale2D = matrix[0][0]
471 if matrix[3][2] != 0.0:
472 matrix[3][0] = matrix[3][0] / (-matrix[3][2] / 100)
473 matrix[3][1] = matrix[3][1] / (-matrix[3][2] / 100)
476 matrix[0][0] = scale2D
477 matrix[1][1] = scale2D
478 matrix[2][2] = scale2D
482 glLoadMatrixf(matrix)
486 def DrawBox(vMin, vMax):
487 glBegin(GL_LINE_LOOP)
488 glVertex3f(vMin[0], vMin[1], vMin[2])
489 glVertex3f(vMax[0], vMin[1], vMin[2])
490 glVertex3f(vMax[0], vMax[1], vMin[2])
491 glVertex3f(vMin[0], vMax[1], vMin[2])
494 glBegin(GL_LINE_LOOP)
495 glVertex3f(vMin[0], vMin[1], vMax[2])
496 glVertex3f(vMax[0], vMin[1], vMax[2])
497 glVertex3f(vMax[0], vMax[1], vMax[2])
498 glVertex3f(vMin[0], vMax[1], vMax[2])
501 glVertex3f(vMin[0], vMin[1], vMin[2])
502 glVertex3f(vMin[0], vMin[1], vMax[2])
503 glVertex3f(vMax[0], vMin[1], vMin[2])
504 glVertex3f(vMax[0], vMin[1], vMax[2])
505 glVertex3f(vMax[0], vMax[1], vMin[2])
506 glVertex3f(vMax[0], vMax[1], vMax[2])
507 glVertex3f(vMin[0], vMax[1], vMin[2])
508 glVertex3f(vMin[0], vMax[1], vMax[2])
512 def DrawMeshOutline(mesh):
513 glEnable(GL_CULL_FACE)
514 glEnableClientState(GL_VERTEX_ARRAY);
515 glVertexPointer(3, GL_FLOAT, 0, mesh.vertexes)
519 glPolygonMode(GL_BACK, GL_LINE)
520 glDrawArrays(GL_TRIANGLES, 0, mesh.vertexCount)
521 glPolygonMode(GL_BACK, GL_FILL)
524 glDisableClientState(GL_VERTEX_ARRAY)
527 def DrawMesh(mesh, insideOut = False):
528 glEnable(GL_CULL_FACE)
529 glEnableClientState(GL_VERTEX_ARRAY)
530 glEnableClientState(GL_NORMAL_ARRAY)
531 for m in mesh._meshList:
532 glVertexPointer(3, GL_FLOAT, 0, m.vertexes)
534 glNormalPointer(GL_FLOAT, 0, m.invNormal)
536 glNormalPointer(GL_FLOAT, 0, m.normal)
538 #Odd, drawing in batchs is a LOT faster then drawing it all at once.
539 batchSize = 999 #Warning, batchSize needs to be dividable by 3
540 extraStartPos = int(m.vertexCount / batchSize) * batchSize
541 extraCount = m.vertexCount - extraStartPos
544 for i in xrange(0, int(m.vertexCount / batchSize)):
545 glDrawArrays(GL_TRIANGLES, i * batchSize, batchSize)
546 glDrawArrays(GL_TRIANGLES, extraStartPos, extraCount)
550 glNormalPointer(GL_FLOAT, 0, m.normal)
552 glNormalPointer(GL_FLOAT, 0, m.invNormal)
553 for i in xrange(0, int(m.vertexCount / batchSize)):
554 glDrawArrays(GL_TRIANGLES, i * batchSize, batchSize)
555 extraStartPos = int(m.vertexCount / batchSize) * batchSize
556 extraCount = m.vertexCount - extraStartPos
557 glDrawArrays(GL_TRIANGLES, extraStartPos, extraCount)
560 glDisableClientState(GL_VERTEX_ARRAY)
561 glDisableClientState(GL_NORMAL_ARRAY)
564 def DrawMeshSteep(mesh, matrix, angle):
565 cosAngle = math.sin(angle / 180.0 * math.pi)
566 glDisable(GL_LIGHTING)
567 glDepthFunc(GL_EQUAL)
568 normals = (numpy.matrix(mesh.normal, copy = False) * matrix).getA()
569 for i in xrange(0, int(mesh.vertexCount), 3):
570 if normals[i][2] < -0.999999:
571 if mesh.vertexes[i + 0][2] > 0.01:
573 glBegin(GL_TRIANGLES)
574 glVertex3f(mesh.vertexes[i + 0][0], mesh.vertexes[i + 0][1], mesh.vertexes[i + 0][2])
575 glVertex3f(mesh.vertexes[i + 1][0], mesh.vertexes[i + 1][1], mesh.vertexes[i + 1][2])
576 glVertex3f(mesh.vertexes[i + 2][0], mesh.vertexes[i + 2][1], mesh.vertexes[i + 2][2])
578 elif normals[i][2] < -cosAngle:
579 glColor3f(-normals[i][2], 0, 0)
580 glBegin(GL_TRIANGLES)
581 glVertex3f(mesh.vertexes[i + 0][0], mesh.vertexes[i + 0][1], mesh.vertexes[i + 0][2])
582 glVertex3f(mesh.vertexes[i + 1][0], mesh.vertexes[i + 1][1], mesh.vertexes[i + 1][2])
583 glVertex3f(mesh.vertexes[i + 2][0], mesh.vertexes[i + 2][1], mesh.vertexes[i + 2][2])
585 elif normals[i][2] > 0.999999:
586 if mesh.vertexes[i + 0][2] > 0.01:
588 glBegin(GL_TRIANGLES)
589 glVertex3f(mesh.vertexes[i + 0][0], mesh.vertexes[i + 0][1], mesh.vertexes[i + 0][2])
590 glVertex3f(mesh.vertexes[i + 2][0], mesh.vertexes[i + 2][1], mesh.vertexes[i + 2][2])
591 glVertex3f(mesh.vertexes[i + 1][0], mesh.vertexes[i + 1][1], mesh.vertexes[i + 1][2])
593 elif normals[i][2] > cosAngle:
594 glColor3f(normals[i][2], 0, 0)
595 glBegin(GL_TRIANGLES)
596 glVertex3f(mesh.vertexes[i + 0][0], mesh.vertexes[i + 0][1], mesh.vertexes[i + 0][2])
597 glVertex3f(mesh.vertexes[i + 2][0], mesh.vertexes[i + 2][1], mesh.vertexes[i + 2][2])
598 glVertex3f(mesh.vertexes[i + 1][0], mesh.vertexes[i + 1][1], mesh.vertexes[i + 1][2])
602 def DrawGCodeLayer(layer, drawQuick = True):
603 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
604 filamentArea = math.pi * filamentRadius * filamentRadius
605 lineWidth = profile.getProfileSettingFloat('nozzle_size') / 2 / 10
608 fillColorCycle = [[0.5, 0.5, 0.0, 1], [0.0, 0.5, 0.5, 1], [0.5, 0.0, 0.5, 1]]
609 moveColor = [0, 0, 1, 0.5]
610 retractColor = [1, 0, 0.5, 0.5]
611 supportColor = [0, 1, 1, 1]
612 extrudeColor = [[1, 0, 0, 1], [0, 1, 1, 1], [1, 1, 0, 1], [1, 0, 1, 1]]
613 innerWallColor = [0, 1, 0, 1]
614 skirtColor = [0, 0.5, 0.5, 1]
615 prevPathWasRetract = False
617 glDisable(GL_CULL_FACE)
619 if path.type == 'move':
620 if prevPathWasRetract:
627 if path.type == 'extrude':
628 if path.pathType == 'FILL':
629 c = fillColorCycle[fillCycle]
630 fillCycle = (fillCycle + 1) % len(fillColorCycle)
631 elif path.pathType == 'WALL-INNER':
634 elif path.pathType == 'SUPPORT':
636 elif path.pathType == 'SKIRT':
639 c = extrudeColor[path.extruder]
640 if path.type == 'retract':
642 if path.type == 'extrude' and not drawQuick:
645 for i in xrange(0, len(path.points) - 1):
647 v1 = path.points[i + 1]
649 # Calculate line width from ePerDistance (needs layer thickness and filament diameter)
650 dist = (v0 - v1).vsize()
651 if dist > 0 and path.layerThickness > 0:
652 extrusionMMperDist = (v1.e - v0.e) / dist
653 lineWidth = extrusionMMperDist * filamentArea / path.layerThickness / 2 * v1.extrudeAmountMultiply
655 drawLength += (v0 - v1).vsize()
656 normal = (v0 - v1).cross(util3d.Vector3(0, 0, 1))
659 vv2 = v0 + normal * lineWidth
660 vv3 = v1 + normal * lineWidth
661 vv0 = v0 - normal * lineWidth
662 vv1 = v1 - normal * lineWidth
666 glVertex3f(vv0.x, vv0.y, vv0.z - zOffset)
667 glVertex3f(vv1.x, vv1.y, vv1.z - zOffset)
668 glVertex3f(vv3.x, vv3.y, vv3.z - zOffset)
669 glVertex3f(vv2.x, vv2.y, vv2.z - zOffset)
671 if prevNormal is not None:
672 n = (normal + prevNormal)
674 vv4 = v0 + n * lineWidth
675 vv5 = v0 - n * lineWidth
678 glVertex3f(vv2.x, vv2.y, vv2.z - zOffset)
679 glVertex3f(vv4.x, vv4.y, vv4.z - zOffset)
680 glVertex3f(prevVv3.x, prevVv3.y, prevVv3.z - zOffset)
681 glVertex3f(v0.x, v0.y, v0.z - zOffset)
683 glVertex3f(vv0.x, vv0.y, vv0.z - zOffset)
684 glVertex3f(vv5.x, vv5.y, vv5.z - zOffset)
685 glVertex3f(prevVv1.x, prevVv1.y, prevVv1.z - zOffset)
686 glVertex3f(v0.x, v0.y, v0.z - zOffset)
694 glBegin(GL_TRIANGLES)
695 for v in path.points:
696 glVertex3f(v[0], v[1], v[2])
699 if not path.type == 'move':
700 prevPathWasRetract = False
701 #if path.type == 'retract' and path.points[0].almostEqual(path.points[-1]):
702 # prevPathWasRetract = True
703 glEnable(GL_CULL_FACE)