From 10897e2b71cee1f45aa7b646b013002c38027ae5 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Sat, 23 Aug 2025 22:42:47 +0200 Subject: [PATCH] Painting: Show brush-preview when moving the mouse over. CURA-12663 --- plugins/PaintTool/PaintTool.py | 33 ++++++++++++++++++++------------- plugins/PaintTool/PaintView.py | 17 ++++++++++++++++- plugins/PaintTool/paint.shader | 28 ++++++++++++++++++++++++++-- 3 files changed, 62 insertions(+), 16 deletions(-) diff --git a/plugins/PaintTool/PaintTool.py b/plugins/PaintTool/PaintTool.py index d6937e4845..22104a7cb3 100644 --- a/plugins/PaintTool/PaintTool.py +++ b/plugins/PaintTool/PaintTool.py @@ -329,7 +329,6 @@ class PaintTool(Tool): """ super().event(event) - controller = Application.getInstance().getController() node = Selection.getSelectedObject(0) if node is None: return False @@ -355,30 +354,36 @@ class PaintTool(Tool): is_moved = event.type == Event.MouseMoveEvent is_pressed = event.type == Event.MousePressEvent if (is_moved or is_pressed) and self._controller.getToolsEnabled(): - if is_moved and not self._mouse_held: + mouse_evt = cast(MouseEvent, event) + + paintview = self._getPaintView() + if paintview is None: return False - mouse_evt = cast(MouseEvent, event) + if not self._picking_pass: + self._picking_pass = CuraApplication.getInstance().getRenderer().getRenderPass("picking_selected") + if not self._picking_pass: + return False + + world_coords_vec = None + if is_moved: + world_coords_vec = self._picking_pass.getPickedPosition(mouse_evt.x, mouse_evt.y) + paintview.setCursor(world_coords_vec, self._brush_size / 128.0, self._brush_color) + if not self._mouse_held: + self._updateScene(node) + return False + if is_pressed: if MouseEvent.LeftButton not in mouse_evt.buttons: return False else: self._mouse_held = True - paintview = self._getPaintView() - if paintview is None: - return False - if not self._faces_selection_pass: self._faces_selection_pass = CuraApplication.getInstance().getRenderer().getRenderPass("selection_faces") if not self._faces_selection_pass: return False - if not self._picking_pass: - self._picking_pass = CuraApplication.getInstance().getRenderer().getRenderPass("picking_selected") - if not self._picking_pass: - return False - if self._camera is None: self._updateCamera() if self._camera is None: @@ -399,8 +404,10 @@ class PaintTool(Tool): face_id = self._faces_selection_pass.getFaceIdAtPosition(mouse_evt.x, mouse_evt.y) if face_id < 0 or face_id >= self._mesh_transformed_cache.getFaceCount(): return False - world_coords = self._picking_pass.getPickedPosition(mouse_evt.x, mouse_evt.y).getData() + if world_coords_vec is None: + world_coords_vec = self._picking_pass.getPickedPosition(mouse_evt.x, mouse_evt.y) + world_coords = world_coords_vec.getData() if self._last_world_coords is None: self._last_world_coords = world_coords self._last_face_id = face_id diff --git a/plugins/PaintTool/PaintView.py b/plugins/PaintTool/PaintView.py index 22629e340c..de57c073ff 100644 --- a/plugins/PaintTool/PaintView.py +++ b/plugins/PaintTool/PaintView.py @@ -2,11 +2,13 @@ # Cura is released under the terms of the LGPLv3 or higher. import os + from PyQt6.QtCore import QRect -from typing import Optional, List, Tuple, Dict, cast +from typing import Optional, List, Tuple, Dict from PyQt6.QtGui import QImage, QColor, QPainter +from UM.Math.Vector import Vector from cura.CuraApplication import CuraApplication from cura.BuildVolume import BuildVolume from cura.CuraView import CuraView @@ -46,6 +48,10 @@ class PaintView(CuraView): self._force_opaque_mask = QImage(2, 2, QImage.Format.Format_Mono) self._force_opaque_mask.fill(1) + self._cursor_position: Vector = Vector(0.0, 0.0, 0.0) + self._cursor_size: float = 0.0 + self._cursor_color: List[float] = [0.0, 0.0, 0.0, 1.0] + application = CuraApplication.getInstance() application.engineCreatedSignal.connect(self._makePaintModes) self._scene = application.getController().getScene() @@ -78,6 +84,11 @@ class PaintView(CuraView): res.setAlphaChannel(self._force_opaque_mask.scaled(image.width(), image.height())) return res + def setCursor(self, position: Optional[Vector] = None, size: float = -1, color: Optional[str] = None) -> None: + self._cursor_position = position if position is not None else self._cursor_position + self._cursor_size = size if size >= 0 else self._cursor_size + self._cursor_color = self._paint_modes[self._current_paint_type][color].display_color if color is not None else self._cursor_color + def addStroke(self, stroke_mask: QImage, start_x: int, start_y: int, brush_color: str) -> None: if self._current_paint_texture is None or self._current_paint_texture.getImage() is None: return @@ -195,6 +206,10 @@ class PaintView(CuraView): self._paint_shader.setUniformValue("u_bitsRangesStart", self._current_bits_ranges[0]) self._paint_shader.setUniformValue("u_bitsRangesEnd", self._current_bits_ranges[1]) + self._paint_shader.setUniformValue("u_cursorPos", self._cursor_position) + self._paint_shader.setUniformValue("u_cursorSize", self._cursor_size) + self._paint_shader.setUniformValue("u_cursorColor", self._cursor_color) + colors = [paint_type_obj.display_color for paint_type_obj in self._paint_modes[self._current_paint_type].values()] colors_values = [[int(color_part * 255) for color_part in [color.r, color.g, color.b]] for color in colors] self._paint_shader.setUniformValueArray("u_renderColors", colors_values) diff --git a/plugins/PaintTool/paint.shader b/plugins/PaintTool/paint.shader index 1982724910..1bfec2b9fe 100644 --- a/plugins/PaintTool/paint.shader +++ b/plugins/PaintTool/paint.shader @@ -33,6 +33,9 @@ fragment = uniform mediump int u_bitsRangesStart; uniform mediump int u_bitsRangesEnd; uniform mediump vec3 u_renderColors[16]; + uniform highp vec3 u_cursorPos; + uniform highp float u_cursorSize; + uniform lowp vec4 u_cursorColor; varying highp vec3 v_vertex; varying highp vec3 v_normal; @@ -57,8 +60,16 @@ fragment = highp float n_dot_l = mix(0.3, 0.7, dot(normal, light_dir)); final_color += (n_dot_l * diffuse_color); - final_color.a = 1.0; + /* Cursor */ + vec3 diff = v_vertex - u_cursorPos; + float squared_dist = dot(diff, diff); + if (squared_dist <= (u_cursorSize * u_cursorSize)) + { + final_color.rgb = mix(final_color.rgb / 2.0, u_cursorColor.rgb, u_cursorColor.a); + } + /* Output */ + final_color.a = 1.0; frag_color = final_color; } @@ -98,6 +109,9 @@ fragment41core = uniform mediump int u_bitsRangesStart; uniform mediump int u_bitsRangesEnd; uniform mediump vec3 u_renderColors[16]; + uniform highp vec3 u_cursorPos; + uniform highp float u_cursorSize; + uniform lowp vec4 u_cursorColor; in highp vec3 v_vertex; in highp vec3 v_normal; @@ -123,14 +137,24 @@ fragment41core = highp float n_dot_l = mix(0.3, 0.7, dot(normal, light_dir)); final_color += (n_dot_l * diffuse_color); - final_color.a = 1.0; + /* Cursor */ + vec3 diff = v_vertex - u_cursorPos; + float squared_dist = dot(diff, diff); + if (squared_dist <= (u_cursorSize * u_cursorSize)) + { + final_color.rgb = mix(final_color.rgb / 2.0, u_cursorColor.rgb, u_cursorColor.a); + } + /* Output */ + final_color.a = 1.0; frag_color = final_color; } [defaults] u_ambientColor = [0.3, 0.3, 0.3, 1.0] u_texture = 0 +u_cursorSize = 0.0 +u_cursorColor = [0.0, 0.0, 0.0, 0.0] [bindings] u_modelMatrix = model_matrix