mirror of
https://github.com/Ultimaker/Cura.git
synced 2026-03-10 21:36:57 -06:00
Merge branch 'CURA-12660_painting-UI-improvements' into CURA-12449_handling-painted-models-map
This commit is contained in:
commit
9e186af74b
13 changed files with 264 additions and 93 deletions
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
46
cura/CuraRenderer.py
Normal file
46
cura/CuraRenderer.py
Normal file
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
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)
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
Loading…
Add table
Add a link
Reference in a new issue