mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-12-25 00:48:34 -07:00
CURA-12752 The previous method was not efficient enough in case of large models, where a single painting stroke can easily cover almost the whole texture (in bounding box). Reverted to the version where the whole texture is counted, but cached in the SliceableObjectDecorator and updated on timer so that it is not done during painting.
114 lines
4.3 KiB
Python
114 lines
4.3 KiB
Python
# 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())
|
|
|
|
node.callDecoration("setPaintedExtrudersCountDirty")
|
|
|
|
self.mainExtruderChanged.emit(node)
|