mirror of
https://github.com/Ultimaker/Cura.git
synced 2026-03-05 18:14:38 -07:00
Merge branch '5.11' into CURA-12739_5.11-Changelog-and-Whats-New-Pages
This commit is contained in:
commit
8ea4633dc8
5 changed files with 270 additions and 70 deletions
|
|
@ -6,10 +6,10 @@ from typing import Optional, Dict
|
|||
from PyQt6.QtCore import QBuffer
|
||||
from PyQt6.QtGui import QImage, QImageWriter
|
||||
|
||||
import UM.View.GL.Texture
|
||||
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
|
||||
from UM.View.GL.OpenGL import OpenGL
|
||||
from UM.View.GL.Texture import Texture
|
||||
from UM.Signal import Signal
|
||||
|
||||
|
||||
class SliceableObjectDecorator(SceneNodeDecorator):
|
||||
|
|
@ -18,14 +18,20 @@ class SliceableObjectDecorator(SceneNodeDecorator):
|
|||
self._paint_texture = None
|
||||
self._texture_data_mapping: Dict[str, tuple[int, int]] = {}
|
||||
|
||||
self.paintTextureChanged = Signal()
|
||||
|
||||
def isSliceable(self) -> bool:
|
||||
return True
|
||||
|
||||
def getPaintTexture(self) -> Optional[Texture]:
|
||||
return self._paint_texture
|
||||
|
||||
def getPaintTextureChangedSignal(self) -> Signal:
|
||||
return self.paintTextureChanged
|
||||
|
||||
def setPaintTexture(self, texture: Texture) -> None:
|
||||
self._paint_texture = texture
|
||||
self.paintTextureChanged.emit()
|
||||
|
||||
def getTextureDataMapping(self) -> Dict[str, tuple[int, int]]:
|
||||
return self._texture_data_mapping
|
||||
|
|
@ -39,6 +45,7 @@ class SliceableObjectDecorator(SceneNodeDecorator):
|
|||
image = QImage(width, height, QImage.Format.Format_RGB32)
|
||||
image.fill(0)
|
||||
self._paint_texture.setImage(image)
|
||||
self.paintTextureChanged.emit()
|
||||
|
||||
def packTexture(self) -> Optional[bytearray]:
|
||||
if self._paint_texture is None:
|
||||
|
|
|
|||
112
plugins/PaintTool/MultiMaterialExtruderConverter.py
Normal file
112
plugins/PaintTool/MultiMaterialExtruderConverter.py
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
# Copyright (c) 2025 UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import numpy
|
||||
from weakref import WeakKeyDictionary
|
||||
import functools
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Machines.Models.ExtrudersModel import ExtrudersModel
|
||||
from UM.Signal import Signal
|
||||
|
||||
from .PaintCommand import PaintCommand
|
||||
|
||||
|
||||
class MultiMaterialExtruderConverter:
|
||||
"""
|
||||
This class is a single object living in the background, which only job is to watch when extruders of objects
|
||||
are changed and to convert their multi-material painting textures accordingly.
|
||||
"""
|
||||
|
||||
MAX_EXTRUDER_COUNT = 16
|
||||
|
||||
def __init__(self, extruders_model: ExtrudersModel) -> None:
|
||||
application = CuraApplication.getInstance()
|
||||
scene = application.getController().getScene()
|
||||
scene.getRoot().childrenChanged.connect(self._onChildrenChanged)
|
||||
|
||||
self._extruders_model: extruders_model
|
||||
self._watched_nodes: WeakKeyDictionary[SceneNode, tuple[Optional[int], Optional[functools.partial]]] = WeakKeyDictionary()
|
||||
|
||||
self.mainExtruderChanged = Signal()
|
||||
|
||||
def _onChildrenChanged(self, node: SceneNode):
|
||||
if node not in self._watched_nodes and node.callDecoration("isSliceable"):
|
||||
self._watched_nodes[node] = (None, None)
|
||||
node.decoratorsChanged.connect(self._onDecoratorsChanged)
|
||||
self._onDecoratorsChanged(node)
|
||||
|
||||
for child in node.getChildren():
|
||||
self._onChildrenChanged(child)
|
||||
|
||||
def _onDecoratorsChanged(self, node: SceneNode) -> None:
|
||||
if node not in self._watched_nodes:
|
||||
return
|
||||
|
||||
current_extruder, extruder_changed_callback = self._watched_nodes[node]
|
||||
if extruder_changed_callback is None:
|
||||
extruder_changed_signal = node.callDecoration("getActiveExtruderChangedSignal")
|
||||
if extruder_changed_signal is not None:
|
||||
extruder_changed_callback = functools.partial(self._onExtruderChanged, node)
|
||||
extruder_changed_signal.connect(extruder_changed_callback)
|
||||
self._watched_nodes[node] = current_extruder, extruder_changed_callback
|
||||
|
||||
self._onExtruderChanged(node)
|
||||
|
||||
def _onExtruderChanged(self, node: SceneNode) -> None:
|
||||
self._changeMainObjectExtruder(node)
|
||||
|
||||
@staticmethod
|
||||
def getPaintedObjectExtruderNr(node: SceneNode) -> Optional[int]:
|
||||
extruder_stack = node.getPrintingExtruder()
|
||||
if extruder_stack is None:
|
||||
return None
|
||||
|
||||
return extruder_stack.getValue("extruder_nr")
|
||||
|
||||
def _changeMainObjectExtruder(self, node: SceneNode) -> None:
|
||||
if node not in self._watched_nodes:
|
||||
return
|
||||
|
||||
old_extruder_nr, extruder_changed_callback = self._watched_nodes[node]
|
||||
new_extruder_nr = MultiMaterialExtruderConverter.getPaintedObjectExtruderNr(node)
|
||||
if new_extruder_nr == old_extruder_nr:
|
||||
return
|
||||
|
||||
self._watched_nodes[node] = (new_extruder_nr, extruder_changed_callback)
|
||||
|
||||
if old_extruder_nr is None or new_extruder_nr is None:
|
||||
return
|
||||
|
||||
texture = node.callDecoration("getPaintTexture")
|
||||
if texture is None:
|
||||
return
|
||||
|
||||
paint_data_mapping = node.callDecoration("getTextureDataMapping")
|
||||
if paint_data_mapping is None or "extruder" not in paint_data_mapping:
|
||||
return
|
||||
|
||||
bits_range = paint_data_mapping["extruder"]
|
||||
|
||||
image = texture.getImage()
|
||||
image_ptr = image.bits()
|
||||
image_ptr.setsize(image.sizeInBytes())
|
||||
image_array = numpy.frombuffer(image_ptr, dtype=numpy.uint32)
|
||||
|
||||
bit_range_start, bit_range_end = bits_range
|
||||
bit_mask = numpy.uint32(PaintCommand.getBitRangeMask(bits_range))
|
||||
|
||||
target_bits = (image_array & bit_mask) >> bit_range_start
|
||||
target_bits[target_bits == old_extruder_nr] = MultiMaterialExtruderConverter.MAX_EXTRUDER_COUNT
|
||||
target_bits[target_bits == new_extruder_nr] = old_extruder_nr
|
||||
target_bits[target_bits == MultiMaterialExtruderConverter.MAX_EXTRUDER_COUNT] = new_extruder_nr
|
||||
|
||||
image_array &= ~bit_mask
|
||||
image_array |= ((target_bits << bit_range_start) & bit_mask)
|
||||
|
||||
texture.updateImagePart(image.rect())
|
||||
|
||||
self.mainExtruderChanged.emit(node)
|
||||
|
|
@ -13,14 +13,20 @@ from .PaintCommand import PaintCommand
|
|||
class PaintClearCommand(PaintCommand):
|
||||
"""Provides the command that clears all the painting for the current mode"""
|
||||
|
||||
def __init__(self, texture: Texture, bit_range: tuple[int, int]) -> None:
|
||||
def __init__(self, texture: Texture, bit_range: tuple[int, int], set_value: int) -> None:
|
||||
super().__init__(texture, bit_range)
|
||||
self._set_value = set_value
|
||||
|
||||
def id(self) -> int:
|
||||
return 1
|
||||
|
||||
def redo(self) -> None:
|
||||
painter = self._makeClearedTexture()
|
||||
|
||||
if self._set_value > 0:
|
||||
painter.setCompositionMode(QPainter.CompositionMode.RasterOp_SourceOrDestination)
|
||||
painter.fillRect(self._texture.getImage().rect(), QBrush(self._set_value))
|
||||
|
||||
painter.end()
|
||||
|
||||
self._texture.updateImagePart(self._bounding_rect)
|
||||
|
|
|
|||
|
|
@ -52,7 +52,11 @@ class PaintCommand(QUndoCommand):
|
|||
def _clearTextureBits(self, painter: QPainter):
|
||||
raise NotImplementedError()
|
||||
|
||||
def _getBitRangeMask(self) -> int:
|
||||
bit_range_start, bit_range_end = self._bit_range
|
||||
@staticmethod
|
||||
def getBitRangeMask(bit_range: tuple[int, int]) -> int:
|
||||
bit_range_start, bit_range_end = bit_range
|
||||
return (((PaintCommand.FULL_INT32 << (32 - 1 - (bit_range_end - bit_range_start))) & PaintCommand.FULL_INT32) >>
|
||||
(32 - 1 - bit_range_end))
|
||||
|
||||
def _getBitRangeMask(self) -> int:
|
||||
return PaintCommand.getBitRangeMask(self._bit_range)
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@
|
|||
|
||||
import os
|
||||
import math
|
||||
from weakref import WeakKeyDictionary
|
||||
|
||||
from PyQt6.QtCore import QRect, pyqtSignal, Qt, QPoint
|
||||
from PyQt6.QtGui import QImage, QUndoStack, QPainter, QColor, QPainterPath, QBrush, QPen
|
||||
from typing import Optional, List, Tuple, Dict
|
||||
from typing import Optional, Tuple, Dict, List
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
|
|
@ -18,7 +19,6 @@ from UM.PluginRegistry import PluginRegistry
|
|||
from UM.View.GL.ShaderProgram import ShaderProgram
|
||||
from UM.View.GL.Texture import Texture
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
from UM.Scene.Selection import Selection
|
||||
from UM.View.GL.OpenGL import OpenGL
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Math.Color import Color
|
||||
|
|
@ -26,6 +26,7 @@ from UM.Math.Polygon import Polygon
|
|||
|
||||
from .PaintStrokeCommand import PaintStrokeCommand
|
||||
from .PaintClearCommand import PaintClearCommand
|
||||
from .MultiMaterialExtruderConverter import MultiMaterialExtruderConverter
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
|
@ -33,8 +34,6 @@ catalog = i18nCatalog("cura")
|
|||
class PaintView(CuraView):
|
||||
"""View for model-painting."""
|
||||
|
||||
MAX_EXTRUDER_COUNT = 16
|
||||
|
||||
class PaintType:
|
||||
def __init__(self, display_color: Color, value: int):
|
||||
self.display_color: Color = display_color
|
||||
|
|
@ -43,27 +42,53 @@ class PaintView(CuraView):
|
|||
def __init__(self) -> None:
|
||||
super().__init__(use_empty_menu_placeholder = True)
|
||||
self._paint_shader: Optional[ShaderProgram] = None
|
||||
self._current_paint_texture: Optional[Texture] = None
|
||||
self._current_painted_object: Optional[SceneNode] = None
|
||||
self._paint_texture: Optional[Texture] = None
|
||||
self._painted_object: Optional[SceneNode] = None
|
||||
self._previous_paint_texture_rect: Optional[QRect] = None
|
||||
self._cursor_texture: Optional[Texture] = None
|
||||
self._current_bits_ranges: tuple[int, int] = (0, 0)
|
||||
self._current_paint_type = ""
|
||||
self._paint_modes: Dict[str, Dict[str, "PaintView.PaintType"]] = {}
|
||||
self._paint_undo_stacks: Dict[Tuple[SceneNode, str], QUndoStack] = {}
|
||||
self._paint_undo_stacks: WeakKeyDictionary[SceneNode, Dict[str, QUndoStack]] = WeakKeyDictionary()
|
||||
|
||||
application = CuraApplication.getInstance()
|
||||
application.engineCreatedSignal.connect(self._makePaintModes)
|
||||
self._scene = application.getController().getScene()
|
||||
self._scene.getRoot().childrenChanged.connect(self._onChildrenChanged)
|
||||
|
||||
self._extruders_model: Optional[ExtrudersModel] = None
|
||||
self._extruders_converter: Optional[MultiMaterialExtruderConverter] = None
|
||||
|
||||
canUndoChanged = pyqtSignal(bool)
|
||||
canRedoChanged = pyqtSignal(bool)
|
||||
|
||||
def setCurrentPaintedObject(self, current_painted_object: Optional[SceneNode]):
|
||||
self._current_painted_object = current_painted_object
|
||||
if self._painted_object is not None:
|
||||
texture_changed_signal = self._painted_object.callDecoration("getPaintTextureChangedSignal")
|
||||
texture_changed_signal.disconnect(self._onCurrentPaintedObjectTextureChanged)
|
||||
|
||||
self._paint_texture = None
|
||||
self._cursor_texture = None
|
||||
|
||||
self._painted_object = current_painted_object
|
||||
|
||||
if self._painted_object is not None:
|
||||
texture_changed_signal = self._painted_object.callDecoration("getPaintTextureChangedSignal")
|
||||
texture_changed_signal.connect(self._onCurrentPaintedObjectTextureChanged)
|
||||
self._onCurrentPaintedObjectTextureChanged()
|
||||
|
||||
self._updateCurrentBitsRanges()
|
||||
|
||||
def _onCurrentPaintedObjectTextureChanged(self) -> None:
|
||||
paint_texture = self._painted_object.callDecoration("getPaintTexture")
|
||||
self._paint_texture = paint_texture
|
||||
if paint_texture is not None:
|
||||
self._cursor_texture = OpenGL.getInstance().createTexture(paint_texture.getWidth(),
|
||||
paint_texture.getHeight())
|
||||
image = QImage(paint_texture.getWidth(), paint_texture.getHeight(), QImage.Format.Format_ARGB32)
|
||||
image.fill(0)
|
||||
self._cursor_texture.setImage(image)
|
||||
else:
|
||||
self._cursor_texture = None
|
||||
|
||||
def canUndo(self):
|
||||
stack = self._getUndoStack()
|
||||
|
|
@ -74,35 +99,23 @@ class PaintView(CuraView):
|
|||
return stack.canRedo() if stack is not None else False
|
||||
|
||||
def _getUndoStack(self):
|
||||
if self._current_painted_object is None or self._current_paint_type == "":
|
||||
if self._painted_object is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
return self._paint_undo_stacks[(self._current_painted_object, self._current_paint_type)]
|
||||
return self._paint_undo_stacks[self._painted_object][self._current_paint_type]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def _onChildrenChanged(self, root_node: SceneNode):
|
||||
# Gather all the actual nodes that have one or more undo stacks
|
||||
stacks_keys = {}
|
||||
for painted_object, paint_mode in self._paint_undo_stacks:
|
||||
if painted_object in stacks_keys:
|
||||
stacks_keys[painted_object].append(paint_mode)
|
||||
else:
|
||||
stacks_keys[painted_object] = [paint_mode]
|
||||
|
||||
# Now see if any of the nodes have been deleted, i.e. they are no more linked to the root
|
||||
for painted_object, paint_modes in stacks_keys.items():
|
||||
if painted_object.getDepth() == 0:
|
||||
for paint_mode in paint_modes:
|
||||
del self._paint_undo_stacks[(painted_object, paint_mode)]
|
||||
|
||||
def _makePaintModes(self):
|
||||
application = CuraApplication.getInstance()
|
||||
|
||||
self._extruders_model = application.getExtrudersModel()
|
||||
self._extruders_model.modelChanged.connect(self._onExtrudersChanged)
|
||||
|
||||
self._extruders_converter = MultiMaterialExtruderConverter(self._extruders_model)
|
||||
self._extruders_converter.mainExtruderChanged.connect(self._onMainExtruderChanged)
|
||||
|
||||
theme = application.getTheme()
|
||||
usual_types = {"none": self.PaintType(Color(*theme.getColor("paint_normal_area").getRgb()), 0),
|
||||
"preferred": self.PaintType(Color(*theme.getColor("paint_preferred_area").getRgb()), 1),
|
||||
|
|
@ -115,10 +128,18 @@ class PaintView(CuraView):
|
|||
|
||||
self._current_paint_type = "seam"
|
||||
|
||||
def _onMainExtruderChanged(self, node: SceneNode):
|
||||
# Since the affected extruder has changed, the previous material painting commands become irrelevant,
|
||||
# so clear the undo stack of the object, if any
|
||||
try:
|
||||
self._paint_undo_stacks[node]["extruder"].clear()
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def _makeExtrudersColors(self) -> Dict[str, "PaintView.PaintType"]:
|
||||
extruders_colors: Dict[str, "PaintView.PaintType"] = {}
|
||||
|
||||
for extruder_index in range(PaintView.MAX_EXTRUDER_COUNT):
|
||||
for extruder_index in range(MultiMaterialExtruderConverter.MAX_EXTRUDER_COUNT):
|
||||
extruder_item = self._extruders_model.getExtruderItem(extruder_index)
|
||||
if extruder_item is None:
|
||||
extruder_item = self._extruders_model.getExtruderItem(0)
|
||||
|
|
@ -142,11 +163,10 @@ class PaintView(CuraView):
|
|||
if controller.getActiveView() != self:
|
||||
return
|
||||
|
||||
selected_objects = Selection.getAllSelectedObjects()
|
||||
if len(selected_objects) != 1:
|
||||
if self._painted_object is None:
|
||||
return
|
||||
|
||||
controller.getScene().sceneChanged.emit(selected_objects[0])
|
||||
controller.getScene().sceneChanged.emit(self._painted_object)
|
||||
|
||||
def _checkSetup(self):
|
||||
if not self._paint_shader:
|
||||
|
|
@ -192,29 +212,52 @@ class PaintView(CuraView):
|
|||
|
||||
return True
|
||||
|
||||
def _shiftTextureValue(self, value: int) -> int:
|
||||
if self._current_bits_ranges is None:
|
||||
return 0
|
||||
|
||||
bit_range_start, _ = self._current_bits_ranges
|
||||
return value << bit_range_start
|
||||
|
||||
def addStroke(self, stroke_path: List[Polygon], brush_color: str, merge_with_previous: bool) -> None:
|
||||
if self._current_paint_texture is None or self._current_paint_texture.getImage() is None:
|
||||
if self._paint_texture is None or self._paint_texture.getImage() is None:
|
||||
return
|
||||
|
||||
self._prepareDataMapping()
|
||||
stack = self._prepareUndoRedoStack()
|
||||
|
||||
bit_range_start, bit_range_end = self._current_bits_ranges
|
||||
set_value = self._paint_modes[self._current_paint_type][brush_color].value << bit_range_start
|
||||
if stack is None:
|
||||
return
|
||||
|
||||
stack.push(PaintStrokeCommand(self._current_paint_texture,
|
||||
set_value = self._shiftTextureValue(self._paint_modes[self._current_paint_type][brush_color].value)
|
||||
stack.push(PaintStrokeCommand(self._paint_texture,
|
||||
stroke_path,
|
||||
set_value,
|
||||
(bit_range_start, bit_range_end),
|
||||
self._current_bits_ranges,
|
||||
merge_with_previous))
|
||||
|
||||
def clearPaint(self):
|
||||
if self._current_paint_texture is None or self._current_paint_texture.getImage() is None:
|
||||
return
|
||||
def _makeClearCommand(self) -> Optional[PaintClearCommand]:
|
||||
if self._painted_object is None or self._paint_texture is None or self._current_bits_ranges is None:
|
||||
return None
|
||||
|
||||
set_value = 0
|
||||
if self._current_paint_type == "extruder":
|
||||
extruder_stack = self._painted_object.getPrintingExtruder()
|
||||
if extruder_stack is not None:
|
||||
set_value = extruder_stack.getValue("extruder_nr")
|
||||
|
||||
return PaintClearCommand(self._paint_texture, self._current_bits_ranges, set_value)
|
||||
|
||||
def clearPaint(self):
|
||||
self._prepareDataMapping()
|
||||
stack = self._prepareUndoRedoStack()
|
||||
stack.push(PaintClearCommand(self._current_paint_texture, self._current_bits_ranges))
|
||||
|
||||
if stack is None:
|
||||
return
|
||||
|
||||
clear_command = self._makeClearCommand()
|
||||
if clear_command is not None:
|
||||
stack.push(clear_command)
|
||||
|
||||
def undoStroke(self) -> None:
|
||||
stack = self._getUndoStack()
|
||||
|
|
@ -227,8 +270,8 @@ class PaintView(CuraView):
|
|||
stack.redo()
|
||||
|
||||
def getUvTexDimensions(self) -> Tuple[int, int]:
|
||||
if self._current_paint_texture is not None:
|
||||
return self._current_paint_texture.getWidth(), self._current_paint_texture.getHeight()
|
||||
if self._paint_texture is not None:
|
||||
return self._paint_texture.getWidth(), self._paint_texture.getHeight()
|
||||
return 0, 0
|
||||
|
||||
def getPaintType(self) -> str:
|
||||
|
|
@ -238,31 +281,56 @@ class PaintView(CuraView):
|
|||
self._current_paint_type = paint_type
|
||||
self._prepareDataMapping()
|
||||
|
||||
def _prepareUndoRedoStack(self) -> QUndoStack:
|
||||
stack_key = (self._current_painted_object, self._current_paint_type)
|
||||
def _prepareUndoRedoStack(self) -> Optional[QUndoStack]:
|
||||
if self._painted_object is None:
|
||||
return None
|
||||
|
||||
if stack_key not in self._paint_undo_stacks:
|
||||
try:
|
||||
return self._paint_undo_stacks[self._painted_object][self._current_paint_type]
|
||||
except KeyError:
|
||||
stack: QUndoStack = QUndoStack()
|
||||
stack.setUndoLimit(32) # Set a quite low amount since some commands copy the full texture
|
||||
stack.setUndoLimit(16) # Set a quite low amount since some commands copy the full texture
|
||||
stack.canUndoChanged.connect(self.canUndoChanged)
|
||||
stack.canRedoChanged.connect(self.canRedoChanged)
|
||||
self._paint_undo_stacks[stack_key] = stack
|
||||
|
||||
return self._paint_undo_stacks[stack_key]
|
||||
if self._painted_object not in self._paint_undo_stacks:
|
||||
self._paint_undo_stacks[self._painted_object] = {}
|
||||
|
||||
def _prepareDataMapping(self):
|
||||
node = Selection.getAllSelectedObjects()[0]
|
||||
if node is None:
|
||||
self._paint_undo_stacks[self._painted_object][self._current_paint_type] = stack
|
||||
return stack
|
||||
|
||||
def _updateCurrentBitsRanges(self):
|
||||
self._current_bits_ranges = (0, 0)
|
||||
|
||||
if self._painted_object is None:
|
||||
return
|
||||
|
||||
paint_data_mapping = node.callDecoration("getTextureDataMapping")
|
||||
paint_data_mapping = self._painted_object.callDecoration("getTextureDataMapping")
|
||||
if paint_data_mapping is None or self._current_paint_type not in paint_data_mapping:
|
||||
return
|
||||
|
||||
self._current_bits_ranges = paint_data_mapping[self._current_paint_type]
|
||||
|
||||
def _prepareDataMapping(self):
|
||||
if self._painted_object is None:
|
||||
return
|
||||
|
||||
paint_data_mapping = self._painted_object.callDecoration("getTextureDataMapping")
|
||||
|
||||
feature_created = False
|
||||
if self._current_paint_type not in paint_data_mapping:
|
||||
new_mapping = self._add_mapping(paint_data_mapping, len(self._paint_modes[self._current_paint_type]))
|
||||
paint_data_mapping[self._current_paint_type] = new_mapping
|
||||
node.callDecoration("setTextureDataMapping", paint_data_mapping)
|
||||
self._painted_object.callDecoration("setTextureDataMapping", paint_data_mapping)
|
||||
feature_created = True
|
||||
|
||||
self._current_bits_ranges = paint_data_mapping[self._current_paint_type]
|
||||
self._updateCurrentBitsRanges()
|
||||
|
||||
if feature_created and self._current_paint_type == "extruder":
|
||||
# Fill texture extruder with actual mesh extruder
|
||||
clear_command = self._makeClearCommand()
|
||||
if clear_command is not None:
|
||||
clear_command.redo()
|
||||
|
||||
@staticmethod
|
||||
def _add_mapping(actual_mapping: Dict[str, tuple[int, int]], nb_storable_values: int) -> tuple[int, int]:
|
||||
|
|
@ -275,7 +343,7 @@ class PaintView(CuraView):
|
|||
return start_index, end_index
|
||||
|
||||
def beginRendering(self) -> None:
|
||||
if self._current_paint_type not in self._paint_modes:
|
||||
if self._painted_object is None or self._current_paint_type not in self._paint_modes:
|
||||
return
|
||||
|
||||
self._checkSetup()
|
||||
|
|
@ -288,22 +356,25 @@ class PaintView(CuraView):
|
|||
paint_batch = renderer.createRenderBatch(shader=self._paint_shader)
|
||||
renderer.addRenderBatch(paint_batch)
|
||||
|
||||
for node in Selection.getAllSelectedObjects():
|
||||
paint_batch.addItem(node.getWorldTransformation(copy=False), node.getMeshData(), normal_transformation=node.getCachedNormalMatrix())
|
||||
paint_texture = node.callDecoration("getPaintTexture")
|
||||
if paint_texture != self._current_paint_texture and paint_texture is not None:
|
||||
self._current_paint_texture = paint_texture
|
||||
paint_batch.addItem(self._painted_object.getWorldTransformation(copy=False),
|
||||
self._painted_object.getMeshData(),
|
||||
normal_transformation=self._painted_object.getCachedNormalMatrix())
|
||||
|
||||
self._cursor_texture = OpenGL.getInstance().createTexture(paint_texture.getWidth(), paint_texture.getHeight())
|
||||
self._paint_shader.setTexture(0, self._current_paint_texture)
|
||||
image = QImage(paint_texture.getWidth(), paint_texture.getHeight(), QImage.Format.Format_ARGB32)
|
||||
image.fill(0)
|
||||
self._cursor_texture.setImage(image)
|
||||
self._paint_shader.setTexture(1, self._cursor_texture)
|
||||
if self._paint_texture is not None:
|
||||
self._paint_shader.setTexture(0, self._paint_texture)
|
||||
if self._cursor_texture is not None:
|
||||
self._paint_shader.setTexture(1, self._cursor_texture)
|
||||
|
||||
self._paint_shader.setUniformValue("u_bitsRangesStart", self._current_bits_ranges[0])
|
||||
self._paint_shader.setUniformValue("u_bitsRangesEnd", self._current_bits_ranges[1])
|
||||
|
||||
colors = [paint_type_obj.display_color for paint_type_obj in self._paint_modes[self._current_paint_type].values()]
|
||||
if self._current_bits_ranges[0] != self._current_bits_ranges[1]:
|
||||
colors = [paint_type_obj.display_color for paint_type_obj in self._paint_modes[self._current_paint_type].values()]
|
||||
elif self._current_paint_type == "extruder":
|
||||
object_extruder = MultiMaterialExtruderConverter.getPaintedObjectExtruderNr(self._painted_object)
|
||||
colors = [self._paint_modes[self._current_paint_type][str(object_extruder)].display_color]
|
||||
else:
|
||||
colors = [self._paint_modes[self._current_paint_type]["none"].display_color]
|
||||
|
||||
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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue