diff --git a/cura/CuraActions.py b/cura/CuraActions.py index 20c44c7916..0f2878023d 100644 --- a/cura/CuraActions.py +++ b/cura/CuraActions.py @@ -7,11 +7,13 @@ from typing import List, cast from UM.Event import CallFunctionEvent from UM.FlameProfiler import pyqtSlot +from UM.Math.Quaternion import Quaternion from UM.Math.Vector import Vector from UM.Scene.Selection import Selection from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator from UM.Operations.GroupedOperation import GroupedOperation from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation +from UM.Operations.RotateOperation import RotateOperation from UM.Operations.TranslateOperation import TranslateOperation import cura.CuraApplication @@ -73,6 +75,36 @@ class CuraActions(QObject): operation.addOperation(center_operation) operation.push() + # Rotate the selection, so that the face that the mouse-pointer is on, faces the build-plate. + @pyqtSlot() + def bottomFaceSelection(self) -> None: + selected_face = Selection.getSelectedFace() + if not selected_face: + Logger.log("e", "Bottom face operation shouldn't have been called without a selected face.") + return + + original_node, face_id = selected_face + meshdata = original_node.getMeshDataTransformed() + if not meshdata or face_id < 0 or face_id > 0x10001: + return + + rotation_point, face_normal = meshdata.getFacePlane(face_id) + rotation_point_vector = Vector(rotation_point[0], rotation_point[1], rotation_point[2]) + face_normal_vector = Vector(face_normal[0], face_normal[1], face_normal[2]) + rotation_quaternion = Quaternion.rotationTo(face_normal_vector.normalized(), Vector(0.0, -1.0, 0.0)) + + operation = GroupedOperation() + for node in Selection.getAllSelectedObjects(): + current_node = node + parent_node = current_node.getParent() + while parent_node and parent_node.callDecoration("isGroup"): + current_node = parent_node + parent_node = current_node.getParent() + + rotate_operation = RotateOperation(current_node, rotation_quaternion, rotation_point_vector) + operation.addOperation(rotate_operation) + operation.push() + ## Multiply all objects in the selection # # \param count The number of times to multiply the selection. diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py index 4ce8ae7bc4..38bc5eada8 100644 --- a/plugins/SolidView/SolidView.py +++ b/plugins/SolidView/SolidView.py @@ -139,6 +139,10 @@ class SolidView(View): shade_factor * int(material_color[5:7], 16) / 255, 1.0 ] + + # Color the currently selected face-id, 0x10001 is certain to be greater than the largest ID. + face = Selection.getSelectedFace() + uniforms["selected_face"] = 0x10001 if not face or node != face[0] else face[1] except ValueError: pass diff --git a/resources/qml/Actions.qml b/resources/qml/Actions.qml index 7e6afa813d..759d4e1785 100644 --- a/resources/qml/Actions.qml +++ b/resources/qml/Actions.qml @@ -26,6 +26,7 @@ Item property alias deleteSelection: deleteSelectionAction; property alias centerSelection: centerSelectionAction; + property alias bottomFaceSelection: bottomFaceSelectionAction; property alias multiplySelection: multiplySelectionAction; property alias deleteObject: deleteObjectAction; @@ -271,6 +272,15 @@ Item onTriggered: CuraActions.centerSelection(); } + Action + { + id: bottomFaceSelectionAction; + text: catalog.i18nc("@action:inmenu menubar:edit", "Align Selected Face To Bottom"); + enabled: UM.Controller.toolsEnabled && UM.Selection.hasFaceSelected; + // iconName: "NO-ICON-YET"; // TODO? + onTriggered: CuraActions.bottomFaceSelection(); + } + Action { id: multiplySelectionAction; diff --git a/resources/qml/Menus/ContextMenu.qml b/resources/qml/Menus/ContextMenu.qml index cb10d50ce8..5125c0c998 100644 --- a/resources/qml/Menus/ContextMenu.qml +++ b/resources/qml/Menus/ContextMenu.qml @@ -20,6 +20,7 @@ Menu // Selection-related actions. MenuItem { action: Cura.Actions.centerSelection; } MenuItem { action: Cura.Actions.deleteSelection; } + MenuItem { action: Cura.Actions.bottomFaceSelection; } MenuItem { action: Cura.Actions.multiplySelection; } // Extruder selection - only visible if there is more than 1 extruder diff --git a/resources/shaders/overhang.shader b/resources/shaders/overhang.shader index e1c03f7586..cb34f25893 100644 --- a/resources/shaders/overhang.shader +++ b/resources/shaders/overhang.shader @@ -32,6 +32,8 @@ fragment = uniform lowp float u_overhangAngle; uniform lowp vec4 u_overhangColor; + uniform lowp vec4 u_faceColor; + uniform highp int u_faceId; varying highp vec3 f_vertex; varying highp vec3 f_normal; @@ -58,7 +60,7 @@ fragment = highp float NdotR = clamp(dot(viewVector, reflectedLight), 0.0, 1.0); finalColor += pow(NdotR, u_shininess) * u_specularColor; - finalColor = (-normal.y > u_overhangAngle) ? u_overhangColor : finalColor; + finalColor = (u_faceId != gl_PrimitiveID) ? ((-normal.y > u_overhangAngle) ? u_overhangColor : finalColor) : u_faceColor; gl_FragColor = finalColor; gl_FragColor.a = 1.0; @@ -99,6 +101,8 @@ fragment41core = uniform lowp float u_overhangAngle; uniform lowp vec4 u_overhangColor; + uniform lowp vec4 u_faceColor; + uniform highp int u_faceId; in highp vec3 f_vertex; in highp vec3 f_normal; @@ -127,7 +131,7 @@ fragment41core = highp float NdotR = clamp(dot(viewVector, reflectedLight), 0.0, 1.0); finalColor += pow(NdotR, u_shininess) * u_specularColor; - finalColor = (-normal.y > u_overhangAngle) ? u_overhangColor : finalColor; + finalColor = (u_faceId != gl_PrimitiveID) ? ((-normal.y > u_overhangAngle) ? u_overhangColor : finalColor) : u_faceColor; frag_color = finalColor; frag_color.a = 1.0; @@ -138,6 +142,7 @@ u_ambientColor = [0.3, 0.3, 0.3, 1.0] u_diffuseColor = [1.0, 0.79, 0.14, 1.0] u_specularColor = [0.4, 0.4, 0.4, 1.0] u_overhangColor = [1.0, 0.0, 0.0, 1.0] +u_faceColor = [0.0, 0.0, 1.0, 1.0] u_shininess = 20.0 [bindings] @@ -148,6 +153,7 @@ u_normalMatrix = normal_matrix u_viewPosition = view_position u_lightPosition = light_0_position u_diffuseColor = diffuse_color +u_faceId = selected_face [attributes] a_vertex = vertex