Painting: Set color, brush-size, brush-shape.

part of CURA-12543
This commit is contained in:
Remco Burema 2025-05-22 10:14:00 +02:00
parent 3ae85e3e2a
commit a176957fa7
3 changed files with 219 additions and 4 deletions

View file

@ -5,9 +5,11 @@ from typing import cast, Optional
import numpy
from PyQt6.QtCore import Qt
from typing import List, Tuple
from UM.Application import Application
from UM.Event import Event, MouseEvent, KeyEvent
from UM.Logger import Logger
from UM.Scene.Selection import Selection
from UM.Tool import Tool
from cura.PickingPass import PickingPass
@ -26,6 +28,31 @@ class PaintTool(Tool):
self._mesh_transformed_cache = None
self._cache_dirty = True
self._color_str_to_rgba = {
"A": [192, 0, 192, 255],
"B": [232, 128, 0, 255],
"C": [0, 255, 0, 255],
"D": [255, 255, 255, 255],
}
self._brush_size = 10
self._brush_color = "A"
self._brush_shape = "A"
def setPaintType(self, paint_type: str) -> None:
Logger.warning(f"TODO: Implement paint-types ({paint_type}).")
pass
def setBrushSize(self, brush_size: float) -> None:
self._brush_size = int(brush_size)
print(self._brush_size)
def setBrushColor(self, brush_color: str) -> None:
self._brush_color = brush_color
def setBrushShape(self, brush_shape: str) -> None:
self._brush_shape = brush_shape
@staticmethod
def _get_intersect_ratio_via_pt(a, pt, b, c) -> float:
# compute the intersection of (param) A - pt with (param) B - (param) C
@ -52,6 +79,20 @@ class PaintTool(Tool):
def _nodeTransformChanged(self, *args) -> None:
self._cache_dirty = True
def _getBrushPixels(self, mid_x: float, mid_y: float, w: float, h: float) -> List[Tuple[float, float]]:
res = []
include = False
for y in range(-self._brush_size, self._brush_size + 1):
for x in range(-self._brush_size, self._brush_size + 1):
match self._brush_shape:
case "A":
include = True
case "B":
include = x * x + y * y <= self._brush_size * self._brush_size
if include:
res.append((mid_x + (x / w), mid_y + (y / h)))
return res
def event(self, event: Event) -> bool:
"""Handle mouse and keyboard events.
@ -128,9 +169,12 @@ class PaintTool(Tool):
texcoords = wa * ta + wb * tb + wc * tc
paintview = controller.getActiveView()
if paintview.getPluginId() != "PaintTool":
if paintview is None or paintview.getPluginId() != "PaintTool":
return False
paintview.setUvPixel(texcoords[0], texcoords[1], [255, 128, 0, 255])
color = self._color_str_to_rgba[self._brush_color]
w, h = paintview.getUvTexDimensions()
for (x, y) in self._getBrushPixels(texcoords[0], texcoords[1], float(w), float(h)):
paintview.setUvPixel(x, y, color)
return True

View file

@ -1,7 +1,8 @@
// Copyright (c) 2025 UltiMaker
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick
import QtQuick.Layouts
import UM 1.7 as UM
@ -11,4 +12,169 @@ Item
width: childrenRect.width
height: childrenRect.height
UM.I18nCatalog { id: catalog; name: "cura"}
ColumnLayout
{
RowLayout
{
UM.ToolbarButton
{
id: paintTypeA
text: catalog.i18nc("@action:button", "Paint Type A")
toolItem: UM.ColorImage
{
source: UM.Theme.getIcon("Buildplate")
color: UM.Theme.getColor("icon")
}
property bool needBorder: true
z: 2
onClicked: UM.Controller.triggerActionWithData("setPaintType", "A")
}
UM.ToolbarButton
{
id: paintTypeB
text: catalog.i18nc("@action:button", "Paint Type B")
toolItem: UM.ColorImage
{
source: UM.Theme.getIcon("BlackMagic")
color: UM.Theme.getColor("icon")
}
property bool needBorder: true
z: 2
onClicked: UM.Controller.triggerActionWithData("setPaintType", "B")
}
}
RowLayout
{
UM.ToolbarButton
{
id: colorButtonA
text: catalog.i18nc("@action:button", "Color A")
toolItem: UM.ColorImage
{
source: UM.Theme.getIcon("Eye")
color: "purple"
}
property bool needBorder: true
z: 2
onClicked: UM.Controller.triggerActionWithData("setBrushColor", "A")
}
UM.ToolbarButton
{
id: colorButtonB
text: catalog.i18nc("@action:button", "Color B")
toolItem: UM.ColorImage
{
source: UM.Theme.getIcon("Eye")
color: "orange"
}
property bool needBorder: true
z: 2
onClicked: UM.Controller.triggerActionWithData("setBrushColor", "B")
}
UM.ToolbarButton
{
id: colorButtonC
text: catalog.i18nc("@action:button", "Color C")
toolItem: UM.ColorImage
{
source: UM.Theme.getIcon("Eye")
color: "green"
}
property bool needBorder: true
z: 2
onClicked: UM.Controller.triggerActionWithData("setBrushColor", "C")
}
UM.ToolbarButton
{
id: colorButtonD
text: catalog.i18nc("@action:button", "Color D")
toolItem: UM.ColorImage
{
source: UM.Theme.getIcon("Eye")
color: "ghostwhite"
}
property bool needBorder: true
z: 2
onClicked: UM.Controller.triggerActionWithData("setBrushColor", "D")
}
}
RowLayout
{
UM.ToolbarButton
{
id: shapeSquareButton
text: catalog.i18nc("@action:button", "Square Brush")
toolItem: UM.ColorImage
{
source: UM.Theme.getIcon("MeshTypeNormal")
color: UM.Theme.getColor("icon")
}
property bool needBorder: true
z: 2
onClicked: UM.Controller.triggerActionWithData("setBrushShape", "A")
}
UM.ToolbarButton
{
id: shapeCircleButton
text: catalog.i18nc("@action:button", "Round Brush")
toolItem: UM.ColorImage
{
source: UM.Theme.getIcon("CircleOutline")
color: UM.Theme.getColor("icon")
}
property bool needBorder: true
z: 2
onClicked: UM.Controller.triggerActionWithData("setBrushShape", "B")
}
UM.Slider
{
id: shapeSizeSlider
from: 1
to: 50
value: 10
onPressedChanged: function(pressed)
{
if(! pressed)
{
UM.Controller.triggerActionWithData("setBrushSize", shapeSizeSlider.value)
}
}
}
}
}
}

View file

@ -18,18 +18,23 @@ class PaintView(View):
super().__init__()
self._paint_shader = None
self._paint_texture = None
self._tex_width = 256
self._tex_height = 256
def _checkSetup(self):
if not self._paint_shader:
shader_filename = os.path.join(PluginRegistry.getInstance().getPluginPath("PaintTool"), "paint.shader")
self._paint_shader = OpenGL.getInstance().createShaderProgram(shader_filename)
if not self._paint_texture:
self._paint_texture = OpenGL.getInstance().createTexture(256, 256)
self._paint_texture = OpenGL.getInstance().createTexture(self._tex_width, self._tex_height)
self._paint_shader.setTexture(0, self._paint_texture)
def setUvPixel(self, x, y, color) -> None:
self._paint_texture.setPixel(x, y, color)
def getUvTexDimensions(self):
return self._tex_width, self._tex_height
def beginRendering(self) -> None:
renderer = self.getRenderer()
self._checkSetup()