Merge branch 'CURA-12660_painting-UI-improvements' into CURA-12449_handling-painted-models-map
Some checks are pending
conan-package / conan-package (push) Waiting to run
unit-test / Run unit tests (push) Waiting to run

This commit is contained in:
Erwan MATHIEU 2025-08-06 16:34:41 +02:00
commit 08a9bbe52c
7 changed files with 34 additions and 60 deletions

View file

@ -1038,7 +1038,6 @@ class CuraApplication(QtApplication):
# Initialize UI state
controller.setActiveStage("PrepareStage")
controller.setActiveView("SolidView")
controller.setCameraTool("CameraTool")
controller.setSelectionTool("SelectionTool")
@ -2089,9 +2088,7 @@ class CuraApplication(QtApplication):
is_non_sliceable = "." + file_extension in self._non_sliceable_extensions
if is_non_sliceable:
# Need to switch first to the preview stage and then to layer view
self.callLater(lambda: (self.getController().setActiveStage("PreviewStage"),
self.getController().setActiveView("SimulationView")))
self.callLater(lambda: (self.getController().setActiveStage("PreviewStage")))
block_slicing_decorator = BlockSlicingDecorator()
node.addDecorator(block_slicing_decorator)

View file

@ -1,6 +1,8 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional
from PyQt6.QtCore import pyqtProperty, QUrl
from UM.Stage import Stage
@ -13,8 +15,8 @@ from UM.Stage import Stage
# * The MainComponent is the component that will be drawn starting from the bottom of the stageBar and fills the rest
# of the screen.
class CuraStage(Stage):
def __init__(self, parent = None) -> None:
super().__init__(parent)
def __init__(self, parent = None, active_view: Optional[str] = "SolidView") -> None:
super().__init__(parent, active_view = active_view)
@pyqtProperty(str, constant = True)
def stageId(self) -> str:

View file

@ -59,7 +59,8 @@ class PaintTool(Tool):
self.setExposedProperties("PaintType", "BrushSize", "BrushColor", "BrushShape", "BrushExtruder")
Selection.selectionChanged.connect(self._updateIgnoreUnselectedObjects)
Selection.selectionChanged.connect(self._updateActiveView)
self._controller.activeViewChanged.connect(self._updateIgnoreUnselectedObjects)
def _createBrushPen(self) -> QPen:
pen = QPen()
@ -307,14 +308,9 @@ class PaintTool(Tool):
# Make sure the displayed values are updated if the bounding box of the selected mesh(es) changes
if event.type == Event.ToolActivateEvent:
controller.setActiveView("PaintTool") # Because that's the plugin-name, and the view is registered to it.
self._updateIgnoreUnselectedObjects()
return True
if event.type == Event.ToolDeactivateEvent:
controller.setActiveView("SolidView")
CuraApplication.getInstance().getRenderer().getRenderPass("selection").setIgnoreUnselectedObjects(False)
CuraApplication.getInstance().getRenderer().getRenderPass("selection_faces").setIgnoreUnselectedObjects(False)
return True
if event.type == Event.MouseReleaseEvent and self._controller.getToolsEnabled():
@ -407,6 +403,9 @@ class PaintTool(Tool):
return False
def getRequiredExtraRenderingPasses(self) -> list[str]:
return ["selection_faces", "picking_selected"]
@staticmethod
def _updateScene(node: SceneNode = None):
if node is None:
@ -414,11 +413,10 @@ class PaintTool(Tool):
if node is not None:
Application.getInstance().getController().getScene().sceneChanged.emit(node)
def getRequiredExtraRenderingPasses(self) -> list[str]:
return ["selection_faces", "picking_selected"]
def _updateActiveView(self):
self.setActiveView("PaintTool" if len(Selection.getAllSelectedObjects()) == 1 else None)
def _updateIgnoreUnselectedObjects(self):
if self._controller.getActiveTool() is self:
ignore_unselected_objects = len(Selection.getAllSelectedObjects()) == 1
CuraApplication.getInstance().getRenderer().getRenderPass("selection").setIgnoreUnselectedObjects(ignore_unselected_objects)
CuraApplication.getInstance().getRenderer().getRenderPass("selection_faces").setIgnoreUnselectedObjects(ignore_unselected_objects)
ignore_unselected_objects = self._controller.getActiveView().name == "PaintTool"
CuraApplication.getInstance().getRenderer().getRenderPass("selection").setIgnoreUnselectedObjects(ignore_unselected_objects)
CuraApplication.getInstance().getRenderer().getRenderPass("selection_faces").setIgnoreUnselectedObjects(ignore_unselected_objects)

View file

@ -9,9 +9,9 @@ from PyQt6.QtGui import QImage, QColor, QPainter
from cura.CuraApplication import CuraApplication
from cura.BuildVolume import BuildVolume
from cura.CuraView import CuraView
from Machines.Models.ExtrudersModel import ExtrudersModel
from UM.PluginRegistry import PluginRegistry
from UM.View.View import View
from UM.View.GL.ShaderProgram import ShaderProgram
from UM.View.GL.Texture import Texture
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
@ -23,7 +23,7 @@ from UM.Math.Color import Color
catalog = i18nCatalog("cura")
class PaintView(View):
class PaintView(CuraView):
"""View for model-painting."""
UNDO_STACK_SIZE = 1024
@ -34,7 +34,7 @@ class PaintView(View):
self.value: int = value
def __init__(self) -> None:
super().__init__()
super().__init__(use_empty_menu_placeholder = True)
self._paint_shader: Optional[ShaderProgram] = None
self._current_paint_texture: Optional[Texture] = None
self._current_bits_ranges: tuple[int, int] = (0, 0)
@ -51,8 +51,6 @@ class PaintView(View):
application.engineCreatedSignal.connect(self._makePaintModes)
self._scene = application.getController().getScene()
self._solid_view = None
self._extruders_model: Optional[ExtrudersModel] = None
def _makePaintModes(self):
@ -219,18 +217,6 @@ class PaintView(View):
if self._current_paint_type not in self._paint_modes:
return
if self._solid_view is None:
plugin_registry = PluginRegistry.getInstance()
solid_view = plugin_registry.getPluginObject("SolidView")
if isinstance(solid_view, View):
self._solid_view = solid_view
display_objects = Selection.getAllSelectedObjects().copy()
if len(display_objects) != 1 and self._solid_view is not None:
# Display the classic view until a single object is selected
self._solid_view.beginRendering()
return
self._checkSetup()
renderer = self.getRenderer()
@ -241,7 +227,7 @@ class PaintView(View):
paint_batch = renderer.createRenderBatch(shader=self._paint_shader)
renderer.addRenderBatch(paint_batch)
for node in display_objects:
for node in Selection.getAllSelectedObjects():
paint_batch.addItem(node.getWorldTransformation(copy=False), node.getMeshData(), normal_transformation=node.getCachedNormalMatrix())
self._current_paint_texture = node.callDecoration("getPaintTexture")
self._paint_shader.setTexture(0, self._current_paint_texture)

View file

@ -24,25 +24,6 @@ class PreviewStage(CuraStage):
super().__init__(parent)
self._application = application
self._application.engineCreatedSignal.connect(self._engineCreated)
self._previously_active_view = None # type: Optional[View]
def onStageSelected(self) -> None:
"""When selecting the stage, remember which was the previous view so that
we can revert to that view when we go out of the stage later.
"""
self._previously_active_view = self._application.getController().getActiveView()
def onStageDeselected(self) -> None:
"""Called when going to a different stage (away from the Preview Stage).
When going to a different stage, the view should be reverted to what it
was before. Normally, that just reverts it to solid view.
"""
if self._previously_active_view is not None:
self._application.getController().setActiveView(self._previously_active_view.getPluginId())
self._previously_active_view = None
def _engineCreated(self) -> None:
"""Delayed load of the QML files.

View file

@ -172,13 +172,20 @@ class SimulationView(CuraView):
self._updateSliceWarningVisibility()
self.activityChanged.emit()
def getSimulationPass(self) -> SimulationPass:
def getSimulationPass(self) -> Optional[SimulationPass]:
if not self._layer_pass:
renderer = self.getRenderer()
if renderer is None:
return None
# Currently the RenderPass constructor requires a size > 0
# This should be fixed in RenderPass's constructor.
self._layer_pass = SimulationPass(1, 1)
self._compatibility_mode = self._evaluateCompatibilityMode()
self._layer_pass.setSimulationView(self)
self._layer_pass.setEnabled(False)
renderer.addRenderPass(self._layer_pass)
return self._layer_pass
def getCurrentLayer(self) -> int:
@ -734,11 +741,14 @@ class SimulationView(CuraView):
# Make sure the SimulationPass is created
layer_pass = self.getSimulationPass()
if layer_pass is None:
return False
renderer = self.getRenderer()
if renderer is None:
return False
renderer.addRenderPass(layer_pass)
layer_pass.setEnabled(True)
# Make sure the NozzleNode is add to the root
nozzle = self.getNozzleNode()
@ -778,7 +788,7 @@ class SimulationView(CuraView):
return False
if self._layer_pass is not None:
renderer.removeRenderPass(self._layer_pass)
self._layer_pass.setEnabled(False)
if self._composite_pass:
self._composite_pass.setLayerBindings(cast(List[str], self._old_layer_bindings))
self._composite_pass.setCompositeShader(cast(ShaderProgram, self._old_composite_shader))

View file

@ -38,7 +38,7 @@ Cura.ExpandablePopup
{
if (activeView == null)
{
UM.Controller.setActiveView(viewModel.getItem(0).id)
UM.Controller.activeStage.setActiveView(viewModel.getItem(0).id)
}
}
@ -110,7 +110,7 @@ Cura.ExpandablePopup
onClicked:
{
toggleContent()
UM.Controller.setActiveView(id)
UM.Controller.activeStage.setActiveView(id)
}
}
}