From 9d97eb7d594208bde9891b515d59aa6673cf6362 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Tue, 5 Aug 2025 14:07:44 +0200 Subject: [PATCH 1/3] Fix sometimes wrong painting color display CURA-12660 --- plugins/PaintTool/paint.shader | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/PaintTool/paint.shader b/plugins/PaintTool/paint.shader index f2af66ffe6..c1b90b376b 100644 --- a/plugins/PaintTool/paint.shader +++ b/plugins/PaintTool/paint.shader @@ -132,7 +132,7 @@ fragment41core = [defaults] u_ambientColor = [0.3, 0.3, 0.3, 1.0] -u_opacity = 0.5 +u_opacity = 1.0 u_texture = 0 [bindings] From cf24ed91e9650dcde09f87d4d53524b878889b7d Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Tue, 5 Aug 2025 15:55:23 +0200 Subject: [PATCH 2/3] Improve fix for opacity issues CURA-12660 Previous fix caused issues when moving to preview --- plugins/PaintTool/paint.shader | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/plugins/PaintTool/paint.shader b/plugins/PaintTool/paint.shader index c1b90b376b..1982724910 100644 --- a/plugins/PaintTool/paint.shader +++ b/plugins/PaintTool/paint.shader @@ -29,7 +29,6 @@ fragment = uniform mediump vec4 u_ambientColor; uniform highp vec3 u_lightPosition; uniform highp vec3 u_viewPosition; - uniform mediump float u_opacity; uniform sampler2D u_texture; uniform mediump int u_bitsRangesStart; uniform mediump int u_bitsRangesEnd; @@ -58,7 +57,7 @@ fragment = highp float n_dot_l = mix(0.3, 0.7, dot(normal, light_dir)); final_color += (n_dot_l * diffuse_color); - final_color.a = u_opacity; + final_color.a = 1.0; frag_color = final_color; } @@ -95,7 +94,6 @@ fragment41core = uniform mediump vec4 u_ambientColor; uniform highp vec3 u_lightPosition; uniform highp vec3 u_viewPosition; - uniform mediump float u_opacity; uniform sampler2D u_texture; uniform mediump int u_bitsRangesStart; uniform mediump int u_bitsRangesEnd; @@ -125,14 +123,13 @@ fragment41core = highp float n_dot_l = mix(0.3, 0.7, dot(normal, light_dir)); final_color += (n_dot_l * diffuse_color); - final_color.a = u_opacity; + final_color.a = 1.0; frag_color = final_color; } [defaults] u_ambientColor = [0.3, 0.3, 0.3, 1.0] -u_opacity = 1.0 u_texture = 0 [bindings] From e69a4369423aa57cd00d81a4b459fb6f4888c30b Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Wed, 6 Aug 2025 16:15:05 +0200 Subject: [PATCH 3/3] Fix sometimes wrongly displayed view CURA-12660 This required a refactoring of the management of the active view. The previous behavior was that anyone could set the active view, depending on certain conditions. But now we also have a view that is set by a tool, so sometimes the actually set view would be incorrect. Now each Stage requests an active view, and each tool CAN also request an active view. Then the Controller decides which view should actually be active depending on the active stage and tool. --- cura/CuraApplication.py | 5 +---- cura/Stages/CuraStage.py | 6 ++++-- plugins/PaintTool/PaintTool.py | 22 ++++++++++------------ plugins/PaintTool/PaintView.py | 22 ++++------------------ plugins/PreviewStage/PreviewStage.py | 19 ------------------- plugins/SimulationView/SimulationView.py | 16 +++++++++++++--- resources/qml/ViewsSelector.qml | 4 ++-- 7 files changed, 34 insertions(+), 60 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 5ce358080c..57d4773cb3 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -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) diff --git a/cura/Stages/CuraStage.py b/cura/Stages/CuraStage.py index 869ed309dc..8c207db8ad 100644 --- a/cura/Stages/CuraStage.py +++ b/cura/Stages/CuraStage.py @@ -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: diff --git a/plugins/PaintTool/PaintTool.py b/plugins/PaintTool/PaintTool.py index eaeb2dc69b..7a1b25f331 100644 --- a/plugins/PaintTool/PaintTool.py +++ b/plugins/PaintTool/PaintTool.py @@ -58,7 +58,8 @@ class PaintTool(Tool): self.setExposedProperties("PaintType", "BrushSize", "BrushColor", "BrushShape") - Selection.selectionChanged.connect(self._updateIgnoreUnselectedObjects) + Selection.selectionChanged.connect(self._updateActiveView) + self._controller.activeViewChanged.connect(self._updateIgnoreUnselectedObjects) def _createBrushPen(self) -> QPen: pen = QPen() @@ -298,14 +299,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(): @@ -397,6 +393,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: @@ -404,11 +403,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) \ No newline at end of file + 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) \ No newline at end of file diff --git a/plugins/PaintTool/PaintView.py b/plugins/PaintTool/PaintView.py index 73e8a511a6..22629e340c 100644 --- a/plugins/PaintTool/PaintView.py +++ b/plugins/PaintTool/PaintView.py @@ -9,8 +9,8 @@ from PyQt6.QtGui import QImage, QColor, QPainter from cura.CuraApplication import CuraApplication from cura.BuildVolume import BuildVolume +from cura.CuraView import CuraView 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 @@ -22,7 +22,7 @@ from UM.Math.Color import Color catalog = i18nCatalog("cura") -class PaintView(View): +class PaintView(CuraView): """View for model-painting.""" UNDO_STACK_SIZE = 1024 @@ -33,7 +33,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) @@ -50,8 +50,6 @@ class PaintView(View): application.engineCreatedSignal.connect(self._makePaintModes) self._scene = application.getController().getScene() - self._solid_view = None - def _makePaintModes(self): theme = CuraApplication.getInstance().getTheme() usual_types = {"none": self.PaintType(Color(*theme.getColor("paint_normal_area").getRgb()), 0), @@ -179,18 +177,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() @@ -201,7 +187,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) diff --git a/plugins/PreviewStage/PreviewStage.py b/plugins/PreviewStage/PreviewStage.py index 88f432ef9b..3f1a4423b2 100644 --- a/plugins/PreviewStage/PreviewStage.py +++ b/plugins/PreviewStage/PreviewStage.py @@ -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. diff --git a/plugins/SimulationView/SimulationView.py b/plugins/SimulationView/SimulationView.py index 083fc73bf1..5d339e7f74 100644 --- a/plugins/SimulationView/SimulationView.py +++ b/plugins/SimulationView/SimulationView.py @@ -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)) diff --git a/resources/qml/ViewsSelector.qml b/resources/qml/ViewsSelector.qml index e76e5dbb67..b0e31ac532 100644 --- a/resources/qml/ViewsSelector.qml +++ b/resources/qml/ViewsSelector.qml @@ -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) } } }