Cura/cura/PickingPass.py
Erwan MATHIEU ab58dec5d1 Fix unability to paint with visible message box
CURA-12660
When a message box is displayed, some offscreen rendering passes (face selection) render an unpredictable result and we are unable to start painting.
This went through a refactoring of the rendering passes. Since doing the offscreen rendering outside the Qt rendering loop caused some troubles, we now use the rendering passes only inside the Qt rendering loop, so that they work properly. Tools also have the ability to indicate which extra passes they require, so that we don't run all the passes when they are not required.
Since this issue also concerns the support blockers placement and rotation by face selection, they have been updated so that they now also always work.
The face selection mechanism using the Selection class was partially working and used only by the rotation, so now it has been deprecated in favor of the new mechanism.
2025-08-01 13:10:03 +02:00

99 lines
4.1 KiB
Python

# Copyright (c) 2020 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional, TYPE_CHECKING
from UM.Qt.QtApplication import QtApplication
from UM.Logger import Logger
from UM.Math.Vector import Vector
from UM.Resources import Resources
from UM.Scene.Selection import Selection
from UM.View.RenderPass import RenderPass
from UM.View.GL.OpenGL import OpenGL
from UM.View.GL.ShaderProgram import InvalidShaderProgramError
from UM.View.RenderBatch import RenderBatch
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
if TYPE_CHECKING:
from UM.View.GL.ShaderProgram import ShaderProgram
class PickingPass(RenderPass):
"""A :py:class:`Uranium.UM.View.RenderPass` subclass that renders a the distance of selectable objects from the
active camera to a texture.
The texture is used to map a 2d location (eg the mouse location) to a world space position
.. note:: that in order to increase precision, the 24 bit depth value is encoded into all three of the R,G & B channels
"""
def __init__(self, width: int, height: int, only_selected_objects: bool = False) -> None:
super().__init__("picking" if not only_selected_objects else "picking_selected", width, height)
self._renderer = QtApplication.getInstance().getRenderer()
self._shader = None #type: Optional[ShaderProgram]
self._scene = QtApplication.getInstance().getController().getScene()
self._only_selected_objects = only_selected_objects
def render(self) -> None:
if not self._shader:
try:
self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "camera_distance.shader"))
except InvalidShaderProgramError:
Logger.error("Unable to compile shader program: camera_distance.shader")
return
width, height = self.getSize()
self._gl.glViewport(0, 0, width, height)
self._gl.glClearColor(1.0, 1.0, 1.0, 0.0)
self._gl.glClear(self._gl.GL_COLOR_BUFFER_BIT | self._gl.GL_DEPTH_BUFFER_BIT)
# Create a new batch to be rendered
batch = RenderBatch(self._shader)
# Fill up the batch with objects that can be sliced. `
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible() and (not self._only_selected_objects or Selection.isSelected(node)):
batch.addItem(node.getWorldTransformation(copy = False), node.getMeshData(), normal_transformation=node.getCachedNormalMatrix())
self.bind()
batch.render(self._scene.getActiveCamera())
self.release()
def getPickedDepth(self, x: int, y: int) -> float:
"""Get the distance in mm from the camera to at a certain pixel coordinate.
:param x: x component of coordinate vector in pixels
:param y: y component of coordinate vector in pixels
:return: distance in mm from the camera to pixel coordinate
"""
output = self.getOutput()
window_size = self._renderer.getWindowSize()
px = int((0.5 + x / 2.0) * window_size[0])
py = int((0.5 + y / 2.0) * window_size[1])
if px < 0 or px > (output.width() - 1) or py < 0 or py > (output.height() - 1):
return -1
distance = output.pixel(px, py) # distance in micron, from in r, g & b channels
distance = (distance & 0x00ffffff) / 1000. # drop the alpha channel and convert to mm
return distance
def getPickedPosition(self, x: int, y: int) -> Vector:
"""Get the world coordinates of a picked point
:param x: x component of coordinate vector in pixels
:param y: y component of coordinate vector in pixels
:return: vector of the world coordinate
"""
distance = self.getPickedDepth(x, y)
camera = self._scene.getActiveCamera()
if camera:
return camera.getRay(x, y).getPointAlongRay(distance)
return Vector()