mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-08-12 22:19:00 +08:00
Merge remote-tracking branch 'origin/CURA-12543_painting_ux' into CURA-12544_saving-and-loading-painted-files-in-Cura
This commit is contained in:
commit
21443faa92
@ -41,9 +41,12 @@ class PaintTool(Tool):
|
||||
self._brush_size: int = 10
|
||||
self._brush_color: str = "A"
|
||||
self._brush_shape: str = "A"
|
||||
self._brush_pen: Optional[QPen] = None
|
||||
self._brush_pen: QPen = self._createBrushPen()
|
||||
|
||||
self._mouse_held: bool = False
|
||||
self._ctrl_held: bool = False
|
||||
self._shift_held: bool = False
|
||||
|
||||
self._last_text_coords: Optional[Tuple[int, int]] = None
|
||||
|
||||
def _createBrushPen(self) -> QPen:
|
||||
@ -72,7 +75,10 @@ class PaintTool(Tool):
|
||||
painter = QPainter(stroke_image)
|
||||
painter.setRenderHint(QPainter.RenderHint.Antialiasing, False)
|
||||
painter.setPen(self._brush_pen)
|
||||
painter.drawLine(int(x0 - start_x), int(y0 - start_y), int(x1 - start_x), int(y1 - start_y))
|
||||
if xdiff == 0 and ydiff == 0:
|
||||
painter.drawPoint(int(x0 - start_x), int(y0 - start_y))
|
||||
else:
|
||||
painter.drawLine(int(x0 - start_x), int(y0 - start_y), int(x1 - start_x), int(y1 - start_y))
|
||||
painter.end()
|
||||
|
||||
return stroke_image, (start_x, start_y)
|
||||
@ -96,6 +102,20 @@ class PaintTool(Tool):
|
||||
self._brush_shape = brush_shape
|
||||
self._brush_pen = self._createBrushPen()
|
||||
|
||||
def undoStackAction(self, redo_instead: bool) -> bool:
|
||||
paintview = Application.getInstance().getController().getActiveView()
|
||||
if paintview is None or paintview.getPluginId() != "PaintTool":
|
||||
return False
|
||||
paintview = cast(PaintView, paintview)
|
||||
if redo_instead:
|
||||
paintview.redoStroke()
|
||||
else:
|
||||
paintview.undoStroke()
|
||||
nodes = Selection.getAllSelectedObjects()
|
||||
if len(nodes) > 0:
|
||||
Application.getInstance().getController().getScene().sceneChanged.emit(nodes[0])
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def _get_intersect_ratio_via_pt(a: numpy.ndarray, pt: numpy.ndarray, b: numpy.ndarray, c: numpy.ndarray) -> float:
|
||||
# compute the intersection of (param) A - pt with (param) B - (param) C
|
||||
@ -154,6 +174,10 @@ class PaintTool(Tool):
|
||||
super().event(event)
|
||||
|
||||
controller = Application.getInstance().getController()
|
||||
nodes = Selection.getAllSelectedObjects()
|
||||
if len(nodes) <= 0:
|
||||
return False
|
||||
node = nodes[0]
|
||||
|
||||
# Make sure the displayed values are updated if the bounding box of the selected mesh(es) changes
|
||||
if event.type == Event.ToolActivateEvent:
|
||||
@ -166,7 +190,27 @@ class PaintTool(Tool):
|
||||
controller.setActiveView("SolidView")
|
||||
return True
|
||||
|
||||
if event.type == Event.KeyPressEvent and cast(KeyEvent, event).key == KeyEvent.ShiftKey:
|
||||
if event.type == Event.KeyPressEvent:
|
||||
evt = cast(KeyEvent, event)
|
||||
if evt.key == KeyEvent.ControlKey:
|
||||
self._ctrl_held = True
|
||||
return True
|
||||
if evt.key == KeyEvent.ShiftKey:
|
||||
self._shift_held = True
|
||||
return True
|
||||
return False
|
||||
|
||||
if event.type == Event.KeyReleaseEvent:
|
||||
evt = cast(KeyEvent, event)
|
||||
if evt.key == KeyEvent.ControlKey:
|
||||
self._ctrl_held = False
|
||||
return True
|
||||
if evt.key == KeyEvent.ShiftKey:
|
||||
self._shift_held = False
|
||||
return True
|
||||
if evt.key == Qt.Key.Key_L and self._ctrl_held:
|
||||
# NOTE: Ctrl-L is used here instead of Ctrl-Z, as the latter is the application-wide one that takes precedence.
|
||||
return self.undoStackAction(self._shift_held)
|
||||
return False
|
||||
|
||||
if event.type == Event.MouseReleaseEvent and self._controller.getToolsEnabled():
|
||||
@ -201,10 +245,6 @@ class PaintTool(Tool):
|
||||
if not camera:
|
||||
return False
|
||||
|
||||
node = Selection.getAllSelectedObjects()[0]
|
||||
if node is None:
|
||||
return False
|
||||
|
||||
if node != self._node_cache:
|
||||
if self._node_cache is not None:
|
||||
self._node_cache.transformationChanged.disconnect(self._nodeTransformChanged)
|
||||
|
@ -176,5 +176,40 @@ Item
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout
|
||||
{
|
||||
UM.ToolbarButton
|
||||
{
|
||||
id: undoButton
|
||||
|
||||
text: catalog.i18nc("@action:button", "Undo Stroke")
|
||||
toolItem: UM.ColorImage
|
||||
{
|
||||
source: UM.Theme.getIcon("ArrowReset")
|
||||
}
|
||||
property bool needBorder: true
|
||||
|
||||
z: 2
|
||||
|
||||
onClicked: UM.Controller.triggerActionWithData("undoStackAction", false)
|
||||
}
|
||||
|
||||
UM.ToolbarButton
|
||||
{
|
||||
id: redoButton
|
||||
|
||||
text: catalog.i18nc("@action:button", "Redo Stroke")
|
||||
toolItem: UM.ColorImage
|
||||
{
|
||||
source: UM.Theme.getIcon("ArrowDoubleCircleRight")
|
||||
}
|
||||
property bool needBorder: true
|
||||
|
||||
z: 2
|
||||
|
||||
onClicked: UM.Controller.triggerActionWithData("undoStackAction", true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,13 @@
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import os
|
||||
from PyQt6.QtGui import QImage
|
||||
from typing import Optional, List, Tuple
|
||||
|
||||
from PyQt6.QtGui import QImage, QColor, QPainter
|
||||
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.View.GL.ShaderProgram import ShaderProgram
|
||||
from UM.View.GL.Texture import Texture
|
||||
from UM.View.View import View
|
||||
from UM.Scene.Selection import Selection
|
||||
from UM.View.GL.OpenGL import OpenGL
|
||||
@ -16,19 +20,63 @@ catalog = i18nCatalog("cura")
|
||||
class PaintView(View):
|
||||
"""View for model-painting."""
|
||||
|
||||
UNDO_STACK_SIZE = 1024
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._paint_shader = None
|
||||
self._current_paint_texture = None
|
||||
self._paint_shader: Optional[ShaderProgram] = None
|
||||
self._current_paint_texture: Optional[Texture] = None
|
||||
|
||||
self._stroke_undo_stack: List[Tuple[QImage, int, int]] = []
|
||||
self._stroke_redo_stack: List[Tuple[QImage, int, int]] = []
|
||||
|
||||
self._force_opaque_mask = QImage(2, 2, QImage.Format.Format_Mono)
|
||||
self._force_opaque_mask.fill(1)
|
||||
|
||||
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)
|
||||
|
||||
def _forceOpaqueDeepCopy(self, image: QImage):
|
||||
res = QImage(image.width(), image.height(), QImage.Format.Format_RGBA8888)
|
||||
res.fill(QColor(255, 255, 255, 255))
|
||||
painter = QPainter(res)
|
||||
painter.setRenderHint(QPainter.RenderHint.Antialiasing, False)
|
||||
painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceOver)
|
||||
painter.drawImage(0, 0, image)
|
||||
painter.end()
|
||||
res.setAlphaChannel(self._force_opaque_mask.scaled(image.width(), image.height()))
|
||||
return res
|
||||
|
||||
def addStroke(self, stroke_image: QImage, start_x: int, start_y: int) -> None:
|
||||
if self._current_paint_texture is not None:
|
||||
self._current_paint_texture.setSubImage(stroke_image, start_x, start_y)
|
||||
if self._current_paint_texture is None:
|
||||
return
|
||||
|
||||
self._stroke_redo_stack.clear()
|
||||
if len(self._stroke_undo_stack) >= PaintView.UNDO_STACK_SIZE:
|
||||
self._stroke_undo_stack.pop(0)
|
||||
undo_image = self._forceOpaqueDeepCopy(self._current_paint_texture.setSubImage(stroke_image, start_x, start_y))
|
||||
if undo_image is not None:
|
||||
self._stroke_undo_stack.append((undo_image, start_x, start_y))
|
||||
|
||||
def _applyUndoStacksAction(self, from_stack: List[Tuple[QImage, int, int]], to_stack: List[Tuple[QImage, int, int]]) -> bool:
|
||||
if len(from_stack) <= 0 or self._current_paint_texture is None:
|
||||
return False
|
||||
from_image, x, y = from_stack.pop()
|
||||
to_image = self._forceOpaqueDeepCopy(self._current_paint_texture.setSubImage(from_image, x, y))
|
||||
if to_image is None:
|
||||
return False
|
||||
if len(to_stack) >= PaintView.UNDO_STACK_SIZE:
|
||||
to_stack.pop(0)
|
||||
to_stack.append((to_image, x, y))
|
||||
return True
|
||||
|
||||
def undoStroke(self) -> bool:
|
||||
return self._applyUndoStacksAction(self._stroke_undo_stack, self._stroke_redo_stack)
|
||||
|
||||
def redoStroke(self) -> bool:
|
||||
return self._applyUndoStacksAction(self._stroke_redo_stack, self._stroke_undo_stack)
|
||||
|
||||
def getUvTexDimensions(self):
|
||||
if self._current_paint_texture is not None:
|
||||
|
Loading…
x
Reference in New Issue
Block a user