PreviewPass.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. # Copyright (c) 2021 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. from typing import Optional, TYPE_CHECKING, cast, List
  4. from UM.Application import Application
  5. from UM.Logger import Logger
  6. from UM.Resources import Resources
  7. from UM.View.RenderPass import RenderPass
  8. from UM.View.GL.OpenGL import OpenGL
  9. from UM.View.RenderBatch import RenderBatch
  10. from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
  11. from cura.Scene.CuraSceneNode import CuraSceneNode
  12. if TYPE_CHECKING:
  13. from UM.View.GL.ShaderProgram import ShaderProgram
  14. from UM.Scene.Camera import Camera
  15. def prettier_color(color_list: List[float]) -> List[float]:
  16. """Make color brighter by normalizing
  17. maximum factor 2.5 brighter
  18. :param color_list: a list of 4 elements: [r, g, b, a], each element is a float 0..1
  19. :return: a normalized list of 4 elements: [r, g, b, a], each element is a float 0..1
  20. """
  21. maximum = max(color_list[:3])
  22. if maximum > 0:
  23. factor = min(1 / maximum, 2.5)
  24. else:
  25. factor = 1.0
  26. return [min(i * factor, 1.0) for i in color_list]
  27. class PreviewPass(RenderPass):
  28. """A :py:class:`Uranium.UM.View.RenderPass` subclass that renders slicable objects with default parameters.
  29. It uses the active camera by default, but it can be overridden to use a different camera.
  30. This is useful to get a preview image of a scene taken from a different location as the active camera.
  31. """
  32. def __init__(self, width: int, height: int, *, root: CuraSceneNode = None) -> None:
  33. super().__init__("preview", width, height, 0)
  34. self._camera: Optional[Camera] = None
  35. self._renderer = Application.getInstance().getRenderer()
  36. self._shader: Optional[ShaderProgram] = None
  37. self._non_printing_shader: Optional[ShaderProgram] = None
  38. self._support_mesh_shader: Optional[ShaderProgram] = None
  39. self._root = Application.getInstance().getController().getScene().getRoot() if root is None else root
  40. # Set the camera to be used by this render pass
  41. # if it's None, the active camera is used
  42. def setCamera(self, camera: Optional["Camera"]):
  43. self._camera = camera
  44. def render(self) -> None:
  45. if not self._shader:
  46. self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "overhang.shader"))
  47. if self._shader:
  48. self._shader.setUniformValue("u_overhangAngle", 1.0)
  49. self._shader.setUniformValue("u_ambientColor", [0.1, 0.1, 0.1, 1.0])
  50. self._shader.setUniformValue("u_specularColor", [0.6, 0.6, 0.6, 1.0])
  51. self._shader.setUniformValue("u_shininess", 20.0)
  52. self._shader.setUniformValue("u_renderError", 0.0) # We don't want any error markers!.
  53. self._shader.setUniformValue("u_faceId", -1) # Don't render any selected faces in the preview.
  54. else:
  55. Logger.error("Unable to compile shader program: overhang.shader")
  56. return
  57. if not self._non_printing_shader:
  58. self._non_printing_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "transparent_object.shader"))
  59. if self._non_printing_shader:
  60. self._non_printing_shader.setUniformValue("u_diffuseColor", [0.5, 0.5, 0.5, 0.5])
  61. self._non_printing_shader.setUniformValue("u_opacity", 0.6)
  62. if not self._support_mesh_shader:
  63. self._support_mesh_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "striped.shader"))
  64. if self._support_mesh_shader:
  65. self._support_mesh_shader.setUniformValue("u_vertical_stripes", True)
  66. self._support_mesh_shader.setUniformValue("u_width", 5.0)
  67. self._gl.glClearColor(0.0, 0.0, 0.0, 0.0)
  68. self._gl.glClear(self._gl.GL_COLOR_BUFFER_BIT | self._gl.GL_DEPTH_BUFFER_BIT)
  69. # Create batches to be rendered
  70. batch = RenderBatch(self._shader)
  71. batch_support_mesh = RenderBatch(self._support_mesh_shader)
  72. # Fill up the batch with objects that can be sliced.
  73. for node in DepthFirstIterator(self._root):
  74. if hasattr(node, "_outside_buildarea") and not getattr(node, "_outside_buildarea"):
  75. if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible():
  76. per_mesh_stack = node.callDecoration("getStack")
  77. if node.callDecoration("isNonThumbnailVisibleMesh"):
  78. # Non printing mesh
  79. continue
  80. elif per_mesh_stack is not None and per_mesh_stack.getProperty("support_mesh", "value"):
  81. # Support mesh
  82. uniforms = {}
  83. shade_factor = 0.6
  84. diffuse_color = cast(CuraSceneNode, node).getDiffuseColor()
  85. diffuse_color2 = [
  86. diffuse_color[0] * shade_factor,
  87. diffuse_color[1] * shade_factor,
  88. diffuse_color[2] * shade_factor,
  89. 1.0]
  90. uniforms["diffuse_color"] = prettier_color(diffuse_color)
  91. uniforms["diffuse_color_2"] = diffuse_color2
  92. batch_support_mesh.addItem(node.getWorldTransformation(copy = False), node.getMeshData(), uniforms = uniforms)
  93. else:
  94. # Normal scene node
  95. uniforms = {}
  96. uniforms["diffuse_color"] = prettier_color(cast(CuraSceneNode, node).getDiffuseColor())
  97. batch.addItem(node.getWorldTransformation(copy = False), node.getMeshData(), uniforms = uniforms)
  98. self.bind()
  99. if self._camera is None:
  100. render_camera = Application.getInstance().getController().getScene().getActiveCamera()
  101. else:
  102. render_camera = self._camera
  103. batch.render(render_camera)
  104. batch_support_mesh.render(render_camera)
  105. self.release()