diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 491d68630e..5ce358080c 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -59,6 +59,7 @@ from cura import ApplicationMetadata from cura.API import CuraAPI from cura.API.Account import Account from cura.Arranging.ArrangeObjectsJob import ArrangeObjectsJob +from cura.CuraRenderer import CuraRenderer from cura.Machines.MachineErrorChecker import MachineErrorChecker from cura.Machines.Models.BuildPlateModel import BuildPlateModel from cura.Machines.Models.CustomQualityProfilesDropDownMenuModel import CustomQualityProfilesDropDownMenuModel @@ -361,6 +362,9 @@ class CuraApplication(QtApplication): self._machine_action_manager = MachineActionManager(self) self._machine_action_manager.initialize() + def makeRenderer(self) -> CuraRenderer: + return CuraRenderer(self) + def __sendCommandToSingleInstance(self): self._single_instance = SingleInstance(self, self._files_to_open, self._urls_to_open) diff --git a/cura/CuraRenderer.py b/cura/CuraRenderer.py new file mode 100644 index 0000000000..77030b3fe8 --- /dev/null +++ b/cura/CuraRenderer.py @@ -0,0 +1,46 @@ +# Copyright (c) 2025 UltiMaker +# Uranium is released under the terms of the LGPLv3 or higher. + + +from typing import TYPE_CHECKING + +from cura.PickingPass import PickingPass +from UM.Qt.QtRenderer import QtRenderer +from UM.View.RenderPass import RenderPass +from UM.View.SelectionPass import SelectionPass + +if TYPE_CHECKING: + from cura.CuraApplication import CuraApplication + + +class CuraRenderer(QtRenderer): + """An overridden Renderer implementation that adds some behaviors specific to Cura.""" + + def __init__(self, application: "CuraApplication") -> None: + super().__init__() + + self._controller = application.getController() + self._controller.activeToolChanged.connect(self._onActiveToolChanged) + self._extra_rendering_passes: list[RenderPass] = [] + + def _onActiveToolChanged(self) -> None: + tool_extra_rendering_passes = [] + + active_tool = self._controller.getActiveTool() + if active_tool is not None: + tool_extra_rendering_passes = active_tool.getRequiredExtraRenderingPasses() + + for extra_rendering_pass in self._extra_rendering_passes: + extra_rendering_pass.setEnabled(extra_rendering_pass.getName() in tool_extra_rendering_passes) + + def _makeRenderPasses(self) -> list[RenderPass]: + self._extra_rendering_passes = [ + SelectionPass(self._viewport_width, self._viewport_height, SelectionPass.SelectionMode.FACES), + PickingPass(self._viewport_width, self._viewport_height, only_selected_objects=True), + PickingPass(self._viewport_width, self._viewport_height, only_selected_objects=False) + ] + + for extra_rendering_pass in self._extra_rendering_passes: + extra_rendering_pass.setEnabled(False) + + return super()._makeRenderPasses() + self._extra_rendering_passes diff --git a/cura/PickingPass.py b/cura/PickingPass.py index 4d6ef671df..e585e72269 100644 --- a/cura/PickingPass.py +++ b/cura/PickingPass.py @@ -7,6 +7,7 @@ from UM.Qt.QtApplication import QtApplication from UM.Logger import Logger from UM.Math.Vector import Vector from UM.Resources import Resources +from UM.Scene.Selection import Selection from UM.View.RenderPass import RenderPass from UM.View.GL.OpenGL import OpenGL @@ -27,13 +28,14 @@ class PickingPass(RenderPass): .. note:: that in order to increase precision, the 24 bit depth value is encoded into all three of the R,G & B channels """ - def __init__(self, width: int, height: int) -> None: - super().__init__("picking", width, height) + def __init__(self, width: int, height: int, only_selected_objects: bool = False) -> None: + super().__init__("picking" if not only_selected_objects else "picking_selected", width, height) self._renderer = QtApplication.getInstance().getRenderer() self._shader = None #type: Optional[ShaderProgram] self._scene = QtApplication.getInstance().getController().getScene() + self._only_selected_objects = only_selected_objects def render(self) -> None: if not self._shader: @@ -53,7 +55,7 @@ class PickingPass(RenderPass): # Fill up the batch with objects that can be sliced. ` for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. - if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible(): + if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible() and (not self._only_selected_objects or Selection.isSelected(node)): batch.addItem(node.getWorldTransformation(copy = False), node.getMeshData(), normal_transformation=node.getCachedNormalMatrix()) self.bind() diff --git a/cura/XRayPass.py b/cura/XRayPass.py index 965294ba89..20fe38741e 100644 --- a/cura/XRayPass.py +++ b/cura/XRayPass.py @@ -16,7 +16,7 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator class XRayPass(RenderPass): def __init__(self, width, height): - super().__init__("xray", width, height) + super().__init__("xray", width, height, -100) self._shader = None self._gl = OpenGL.getInstance().getBindingsObject() diff --git a/plugins/PaintTool/BrushColorButton.qml b/plugins/PaintTool/BrushColorButton.qml index 71556f2681..ae4ab6243f 100644 --- a/plugins/PaintTool/BrushColorButton.qml +++ b/plugins/PaintTool/BrushColorButton.qml @@ -13,13 +13,27 @@ UM.ToolbarButton property string color - checked: base.selectedColor === buttonBrushColor.color - onClicked: setColor() function setColor() { - base.selectedColor = buttonBrushColor.color - UM.Controller.triggerActionWithData("setBrushColor", buttonBrushColor.color) + UM.Controller.setProperty("BrushColor", buttonBrushColor.color); + } + + function isChecked() + { + return UM.Controller.properties.getValue("BrushColor") === buttonBrushColor.color; + } + + Component.onCompleted: + { + buttonBrushColor.checked = isChecked(); + } + + Binding + { + target: buttonBrushColor + property: "checked" + value: isChecked() } } diff --git a/plugins/PaintTool/BrushShapeButton.qml b/plugins/PaintTool/BrushShapeButton.qml index 5c290e4a13..ef4256792a 100644 --- a/plugins/PaintTool/BrushShapeButton.qml +++ b/plugins/PaintTool/BrushShapeButton.qml @@ -13,13 +13,27 @@ UM.ToolbarButton property int shape - checked: base.selectedShape === buttonBrushShape.shape - onClicked: setShape() function setShape() { - base.selectedShape = buttonBrushShape.shape - UM.Controller.triggerActionWithData("setBrushShape", buttonBrushShape.shape) + UM.Controller.setProperty("BrushShape", buttonBrushShape.shape) + } + + function isChecked() + { + return UM.Controller.properties.getValue("BrushShape") === buttonBrushShape.shape; + } + + Component.onCompleted: + { + buttonBrushShape.checked = isChecked(); + } + + Binding + { + target: buttonBrushShape + property: "checked" + value: isChecked() } } diff --git a/plugins/PaintTool/PaintModeButton.qml b/plugins/PaintTool/PaintModeButton.qml index 473996e04b..eb294f7ad6 100644 --- a/plugins/PaintTool/PaintModeButton.qml +++ b/plugins/PaintTool/PaintModeButton.qml @@ -6,19 +6,34 @@ import QtQuick import UM 1.7 as UM import Cura 1.0 as Cura + Cura.ModeSelectorButton { id: modeSelectorButton property string mode - selected: base.selectedMode === modeSelectorButton.mode - onClicked: setMode() function setMode() { - base.selectedMode = modeSelectorButton.mode - UM.Controller.triggerActionWithData("setPaintType", modeSelectorButton.mode) + UM.Controller.setProperty("PaintType", modeSelectorButton.mode); + } + + function isSelected() + { + return UM.Controller.properties.getValue("PaintType") === modeSelectorButton.mode; + } + + Component.onCompleted: + { + modeSelectorButton.selected = isSelected(); + } + + Binding + { + target: modeSelectorButton + property: "selected" + value: isSelected() } } diff --git a/plugins/PaintTool/PaintTool.py b/plugins/PaintTool/PaintTool.py index fa6436f10d..eaeb2dc69b 100644 --- a/plugins/PaintTool/PaintTool.py +++ b/plugins/PaintTool/PaintTool.py @@ -16,8 +16,11 @@ from UM.Logger import Logger from UM.Scene.SceneNode import SceneNode from UM.Scene.Selection import Selection from UM.Tool import Tool +from UM.View.GL.OpenGL import OpenGL +from cura.CuraApplication import CuraApplication from cura.PickingPass import PickingPass +from UM.View.SelectionPass import SelectionPass from .PaintView import PaintView @@ -34,6 +37,7 @@ class PaintTool(Tool): super().__init__() self._picking_pass: Optional[PickingPass] = None + self._faces_selection_pass: Optional[SelectionPass] = None self._shortcut_key: Qt.Key = Qt.Key.Key_P @@ -41,9 +45,9 @@ class PaintTool(Tool): self._mesh_transformed_cache = None self._cache_dirty: bool = True - self._brush_size: int = 10 - self._brush_color: str = "" - self._brush_shape: PaintTool.Brush.Shape = PaintTool.Brush.Shape.SQUARE + self._brush_size: int = 200 + self._brush_color: str = "preferred" + self._brush_shape: PaintTool.Brush.Shape = PaintTool.Brush.Shape.CIRCLE self._brush_pen: QPen = self._createBrushPen() self._mouse_held: bool = False @@ -52,6 +56,10 @@ class PaintTool(Tool): self._last_mouse_coords: Optional[Tuple[int, int]] = None self._last_face_id: Optional[int] = None + self.setExposedProperties("PaintType", "BrushSize", "BrushColor", "BrushShape") + + Selection.selectionChanged.connect(self._updateIgnoreUnselectedObjects) + def _createBrushPen(self) -> QPen: pen = QPen() pen.setWidth(self._brush_size) @@ -86,28 +94,51 @@ class PaintTool(Tool): return stroke_image, (start_x, start_y) + def getPaintType(self) -> str: + paint_view = self._get_paint_view() + if paint_view is None: + return "" + + return paint_view.getPaintType() + def setPaintType(self, paint_type: str) -> None: paint_view = self._get_paint_view() if paint_view is None: return - paint_view.setPaintType(paint_type) + if paint_type != self.getPaintType(): + paint_view.setPaintType(paint_type) - self._brush_pen = self._createBrushPen() - self._updateScene() + self._brush_pen = self._createBrushPen() + self._updateScene() + self.propertyChanged.emit() + + def getBrushSize(self) -> int: + return self._brush_size def setBrushSize(self, brush_size: float) -> None: - if brush_size != self._brush_size: - self._brush_size = int(brush_size) + brush_size_int = int(brush_size) + if brush_size_int != self._brush_size: + self._brush_size = brush_size_int self._brush_pen = self._createBrushPen() + self.propertyChanged.emit() + + def getBrushColor(self) -> str: + return self._brush_color def setBrushColor(self, brush_color: str) -> None: - self._brush_color = brush_color + if brush_color != self._brush_color: + self._brush_color = brush_color + self.propertyChanged.emit() + + def getBrushShape(self) -> int: + return self._brush_shape def setBrushShape(self, brush_shape: int) -> None: if brush_shape != self._brush_shape: self._brush_shape = brush_shape self._brush_pen = self._createBrushPen() + self.propertyChanged.emit() def undoStackAction(self, redo_instead: bool) -> bool: paint_view = self._get_paint_view() @@ -179,7 +210,7 @@ class PaintTool(Tool): self._cache_dirty = True def _getTexCoordsFromClick(self, node: SceneNode, x: float, y: float) -> Tuple[int, Optional[numpy.ndarray]]: - face_id = self._selection_pass.getFaceIdAtPosition(x, y) + face_id = self._faces_selection_pass.getFaceIdAtPosition(x, y) if face_id < 0 or face_id >= node.getMeshData().getFaceCount(): return face_id, None @@ -234,6 +265,23 @@ class PaintTool(Tool): self._iteratateSplitSubstroke(node, substrokes, mid_struct, info_b) self._iteratateSplitSubstroke(node, substrokes, info_a, mid_struct) + def _setupNodeForPainting(self, node: SceneNode) -> bool: + mesh = node.getMeshData() + if mesh.hasUVCoordinates(): + return True + + texture_width, texture_height = mesh.calculateUnwrappedUVCoordinates() + if texture_width <= 0 or texture_height <= 0: + return False + + node.callDecoration("prepareTexture", texture_width, texture_height) + + if hasattr(mesh, OpenGL.VertexBufferProperty): + # Force clear OpenGL buffer so that new UV coordinates will be sent + delattr(mesh, OpenGL.VertexBufferProperty) + + return True + def event(self, event: Event) -> bool: """Handle mouse and keyboard events. @@ -250,13 +298,14 @@ 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.setActiveStage("PrepareStage") 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.setActiveStage("PrepareStage") 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(): @@ -285,8 +334,15 @@ class PaintTool(Tool): if paintview is None: return False - if not self._selection_pass: - return False + if not self._faces_selection_pass: + self._faces_selection_pass = CuraApplication.getInstance().getRenderer().getRenderPass("selection_faces") + if not self._faces_selection_pass: + return False + + if not self._picking_pass: + self._picking_pass = CuraApplication.getInstance().getRenderer().getRenderPass("picking_selected") + if not self._picking_pass: + return False camera = self._controller.getScene().getActiveCamera() if not camera: @@ -297,17 +353,15 @@ class PaintTool(Tool): self._node_cache.transformationChanged.disconnect(self._nodeTransformChanged) self._node_cache = node self._node_cache.transformationChanged.connect(self._nodeTransformChanged) + self._cache_dirty = True if self._cache_dirty: self._cache_dirty = False self._mesh_transformed_cache = self._node_cache.getMeshDataTransformed() if not self._mesh_transformed_cache: return False - if not self._picking_pass: - self._picking_pass = PickingPass(camera.getViewportWidth(), camera.getViewportHeight()) - self._picking_pass.render() - - self._selection_pass.renderFacesMode() + if not self._setupNodeForPainting(node): + return False face_id, texcoords = self._getTexCoordsFromClick(node, mouse_evt.x, mouse_evt.y) if texcoords is None: @@ -348,4 +402,13 @@ class PaintTool(Tool): if node is None: node = Selection.getSelectedObject(0) if node is not None: - Application.getInstance().getController().getScene().sceneChanged.emit(node) \ No newline at end of file + Application.getInstance().getController().getScene().sceneChanged.emit(node) + + def getRequiredExtraRenderingPasses(self) -> list[str]: + return ["selection_faces", "picking_selected"] + + 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 diff --git a/plugins/PaintTool/PaintTool.qml b/plugins/PaintTool/PaintTool.qml index 2464a3739b..eac5948f26 100644 --- a/plugins/PaintTool/PaintTool.qml +++ b/plugins/PaintTool/PaintTool.qml @@ -15,10 +15,6 @@ Item height: childrenRect.height UM.I18nCatalog { id: catalog; name: "cura"} - property string selectedMode: "" - property string selectedColor: "" - property int selectedShape: 0 - Action { id: undoAction @@ -57,6 +53,7 @@ Item icon: "Support" tooltipText: catalog.i18nc("@tooltip", "Refine support placement by defining preferred/avoidance areas") mode: "support" + visible: false } PaintModeButton @@ -174,19 +171,18 @@ Item from: 10 to: 1000 - value: 200 onPressedChanged: function(pressed) { if(! pressed) { - setBrushSize() + UM.Controller.setProperty("BrushSize", shapeSizeSlider.value); } } - function setBrushSize() + Component.onCompleted: { - UM.Controller.triggerActionWithData("setBrushSize", shapeSizeSlider.value) + shapeSizeSlider.value = UM.Controller.properties.getValue("BrushSize"); } } @@ -239,13 +235,4 @@ Item } } } - - Component.onCompleted: - { - // Force first types for consistency, otherwise UI may become different from controller - rowPaintMode.children[0].setMode() - rowBrushColor.children[1].setColor() - rowBrushShape.children[1].setShape() - shapeSizeSlider.setBrushSize() - } } diff --git a/plugins/PaintTool/PaintView.py b/plugins/PaintTool/PaintView.py index 5f130d448f..1f185ee809 100644 --- a/plugins/PaintTool/PaintView.py +++ b/plugins/PaintTool/PaintView.py @@ -3,15 +3,17 @@ import os from PyQt6.QtCore import QRect -from typing import Optional, List, Tuple, Dict +from typing import Optional, List, Tuple, Dict, cast from PyQt6.QtGui import QImage, QColor, QPainter from cura.CuraApplication import CuraApplication +from cura.BuildVolume import BuildVolume 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.View.View import View +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 @@ -44,7 +46,11 @@ class PaintView(View): self._force_opaque_mask = QImage(2, 2, QImage.Format.Format_Mono) self._force_opaque_mask.fill(1) - CuraApplication.getInstance().engineCreatedSignal.connect(self._makePaintModes) + application = CuraApplication.getInstance() + application.engineCreatedSignal.connect(self._makePaintModes) + self._scene = application.getController().getScene() + + self._solid_view = None def _makePaintModes(self): theme = CuraApplication.getInstance().getTheme() @@ -57,6 +63,8 @@ class PaintView(View): "extruder": usual_types, } + self._current_paint_type = "seam" + def _checkSetup(self): if not self._paint_shader: shader_filename = os.path.join(PluginRegistry.getInstance().getPluginPath("PaintTool"), "paint.shader") @@ -77,6 +85,8 @@ class PaintView(View): if self._current_paint_texture is None or self._current_paint_texture.getImage() is None: return + self._prepareDataMapping() + actual_image = self._current_paint_texture.getImage() bit_range_start, bit_range_end = self._current_bits_ranges @@ -136,30 +146,25 @@ class PaintView(View): return self._current_paint_texture.getWidth(), self._current_paint_texture.getHeight() return 0, 0 + def getPaintType(self) -> str: + return self._current_paint_type + def setPaintType(self, paint_type: str) -> None: + self._current_paint_type = paint_type + + def _prepareDataMapping(self): node = Selection.getAllSelectedObjects()[0] if node is None: return paint_data_mapping = node.callDecoration("getTextureDataMapping") - if paint_type not in paint_data_mapping: - new_mapping = self._add_mapping(paint_data_mapping, len(self._paint_modes[paint_type])) - paint_data_mapping[paint_type] = new_mapping + 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) - mesh = node.getMeshData() - if not mesh.hasUVCoordinates(): - texture_width, texture_height = mesh.calculateUnwrappedUVCoordinates() - if texture_width > 0 and texture_height > 0: - node.callDecoration("prepareTexture", texture_width, texture_height) - - if hasattr(mesh, OpenGL.VertexBufferProperty): - # Force clear OpenGL buffer so that new UV coordinates will be sent - delattr(mesh, OpenGL.VertexBufferProperty) - - self._current_paint_type = paint_type - self._current_bits_ranges = paint_data_mapping[paint_type] + self._current_bits_ranges = paint_data_mapping[self._current_paint_type] @staticmethod def _add_mapping(actual_mapping: Dict[str, tuple[int, int]], nb_storable_values: int) -> tuple[int, int]: @@ -172,17 +177,35 @@ class PaintView(View): return start_index, end_index def beginRendering(self) -> None: - renderer = self.getRenderer() + 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() + + for node in DepthFirstIterator(self._scene.getRoot()): + if isinstance(node, BuildVolume): + node.render(renderer) + paint_batch = renderer.createRenderBatch(shader=self._paint_shader) renderer.addRenderBatch(paint_batch) - node = Selection.getSelectedObject(0) - if node is None: - return - - if self._current_paint_type == "": - return + for node in display_objects: + 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) self._paint_shader.setUniformValue("u_bitsRangesStart", self._current_bits_ranges[0]) self._paint_shader.setUniformValue("u_bitsRangesEnd", self._current_bits_ranges[1]) @@ -190,8 +213,3 @@ class PaintView(View): colors = [paint_type_obj.display_color for paint_type_obj in self._paint_modes[self._current_paint_type].values()] 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) - - self._current_paint_texture = node.callDecoration("getPaintTexture") - self._paint_shader.setTexture(0, self._current_paint_texture) - - paint_batch.addItem(node.getWorldTransformation(copy=False), node.getMeshData(), normal_transformation=node.getCachedNormalMatrix()) diff --git a/plugins/PaintTool/paint.shader b/plugins/PaintTool/paint.shader index bd769f5cb2..c1b90b376b 100644 --- a/plugins/PaintTool/paint.shader +++ b/plugins/PaintTool/paint.shader @@ -55,7 +55,7 @@ fragment = color_index = (color_index << (32 - 1 - u_bitsRangesEnd)) >> 32 - 1 - (u_bitsRangesEnd - u_bitsRangesStart); vec4 diffuse_color = vec4(u_renderColors[color_index] / 255.0, 1.0); - highp float n_dot_l = clamp(dot(normal, light_dir), 0.0, 1.0); + 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; @@ -122,7 +122,7 @@ fragment41core = color_index = (color_index << (32 - 1 - u_bitsRangesEnd)) >> 32 - 1 - (u_bitsRangesEnd - u_bitsRangesStart); vec4 diffuse_color = vec4(u_renderColors[color_index] / 255.0, 1.0); - highp float n_dot_l = clamp(dot(normal, light_dir), 0.0, 1.0); + 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; @@ -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] diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py index bffc3aa526..e25273cb13 100644 --- a/plugins/SolidView/SolidView.py +++ b/plugins/SolidView/SolidView.py @@ -289,8 +289,9 @@ class SolidView(View): def endRendering(self): # check whether the xray overlay is showing badness - if time.time() > self._next_xray_checking_time\ - and Application.getInstance().getPreferences().getValue(self._show_xray_warning_preference): + if (time.time() > self._next_xray_checking_time + and Application.getInstance().getPreferences().getValue(self._show_xray_warning_preference) + and self._xray_pass is not None): self._next_xray_checking_time = time.time() + self._xray_checking_update_time xray_img = self._xray_pass.getOutput() diff --git a/plugins/SupportEraser/SupportEraser.py b/plugins/SupportEraser/SupportEraser.py index 0a714396aa..afdad6a4d0 100644 --- a/plugins/SupportEraser/SupportEraser.py +++ b/plugins/SupportEraser/SupportEraser.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 Qt, QTimer from PyQt6.QtWidgets import QApplication @@ -35,6 +37,7 @@ class SupportEraser(Tool): self._controller = self.getController() self._selection_pass = None + self._picking_pass: Optional[PickingPass] = None CuraApplication.getInstance().globalContainerStackChanged.connect(self._updateEnabled) # Note: if the selection is cleared with this tool active, there is no way to switch to @@ -84,12 +87,13 @@ class SupportEraser(Tool): # Only "normal" meshes can have anti_overhang_meshes added to them return - # Create a pass for picking a world-space location from the mouse location - active_camera = self._controller.getScene().getActiveCamera() - picking_pass = PickingPass(active_camera.getViewportWidth(), active_camera.getViewportHeight()) - picking_pass.render() + # Get the pass for picking a world-space location from the mouse location + if self._picking_pass is None: + self._picking_pass = Application.getInstance().getRenderer().getRenderPass("picking_selected") + if not self._picking_pass: + return - picked_position = picking_pass.getPickedPosition(event.x, event.y) + picked_position = self._picking_pass.getPickedPosition(event.x, event.y) # Add the anti_overhang_mesh cube at the picked location self._createEraserMesh(picked_node, picked_position) @@ -189,3 +193,6 @@ class SupportEraser(Tool): mesh.calculateNormals() return mesh + + def getRequiredExtraRenderingPasses(self) -> list[str]: + return ["picking_selected"] \ No newline at end of file