PickingPass.py 3.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. # Copyright (c) 2020 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. from typing import Optional, TYPE_CHECKING
  4. from UM.Qt.QtApplication import QtApplication
  5. from UM.Logger import Logger
  6. from UM.Math.Vector import Vector
  7. from UM.Resources import Resources
  8. from UM.View.RenderPass import RenderPass
  9. from UM.View.GL.OpenGL import OpenGL
  10. from UM.View.GL.ShaderProgram import InvalidShaderProgramError
  11. from UM.View.RenderBatch import RenderBatch
  12. from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
  13. if TYPE_CHECKING:
  14. from UM.View.GL.ShaderProgram import ShaderProgram
  15. class PickingPass(RenderPass):
  16. """A :py:class:`Uranium.UM.View.RenderPass` subclass that renders a the distance of selectable objects from the
  17. active camera to a texture.
  18. The texture is used to map a 2d location (eg the mouse location) to a world space position
  19. .. note:: that in order to increase precision, the 24 bit depth value is encoded into all three of the R,G & B channels
  20. """
  21. def __init__(self, width: int, height: int) -> None:
  22. super().__init__("picking", width, height)
  23. self._renderer = QtApplication.getInstance().getRenderer()
  24. self._shader = None #type: Optional[ShaderProgram]
  25. self._scene = QtApplication.getInstance().getController().getScene()
  26. def render(self) -> None:
  27. if not self._shader:
  28. try:
  29. self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "camera_distance.shader"))
  30. except InvalidShaderProgramError:
  31. Logger.error("Unable to compile shader program: camera_distance.shader")
  32. return
  33. width, height = self.getSize()
  34. self._gl.glViewport(0, 0, width, height)
  35. self._gl.glClearColor(1.0, 1.0, 1.0, 0.0)
  36. self._gl.glClear(self._gl.GL_COLOR_BUFFER_BIT | self._gl.GL_DEPTH_BUFFER_BIT)
  37. # Create a new batch to be rendered
  38. batch = RenderBatch(self._shader)
  39. # Fill up the batch with objects that can be sliced. `
  40. for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
  41. if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible():
  42. batch.addItem(node.getWorldTransformation(copy = False), node.getMeshData(), normal_transformation=node.getCachedNormalMatrix())
  43. self.bind()
  44. batch.render(self._scene.getActiveCamera())
  45. self.release()
  46. def getPickedDepth(self, x: int, y: int) -> float:
  47. """Get the distance in mm from the camera to at a certain pixel coordinate.
  48. :param x: x component of coordinate vector in pixels
  49. :param y: y component of coordinate vector in pixels
  50. :return: distance in mm from the camera to pixel coordinate
  51. """
  52. output = self.getOutput()
  53. window_size = self._renderer.getWindowSize()
  54. px = (0.5 + x / 2.0) * window_size[0]
  55. py = (0.5 + y / 2.0) * window_size[1]
  56. if px < 0 or px > (output.width() - 1) or py < 0 or py > (output.height() - 1):
  57. return -1
  58. distance = output.pixel(px, py) # distance in micron, from in r, g & b channels
  59. distance = (distance & 0x00ffffff) / 1000. # drop the alpha channel and convert to mm
  60. return distance
  61. def getPickedPosition(self, x: int, y: int) -> Vector:
  62. """Get the world coordinates of a picked point
  63. :param x: x component of coordinate vector in pixels
  64. :param y: y component of coordinate vector in pixels
  65. :return: vector of the world coordinate
  66. """
  67. distance = self.getPickedDepth(x, y)
  68. camera = self._scene.getActiveCamera()
  69. if camera:
  70. return camera.getRay(x, y).getPointAlongRay(distance)
  71. return Vector()