From b97015a35407e77c7d66c5f7aad8ca486e05e54e Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Mon, 2 Sep 2019 00:17:14 +0200 Subject: [PATCH 1/7] Added 'align selected face with buildplate' feature. Alt-click to select a face. See the (identically named) 'feature_bottom_face' branch in Uranium for more indepth information. --- cura/CuraActions.py | 32 +++++++++++++++++++++++++++++ plugins/SolidView/SolidView.py | 4 ++++ resources/qml/Actions.qml | 10 +++++++++ resources/qml/Menus/ContextMenu.qml | 1 + resources/shaders/overhang.shader | 10 +++++++-- 5 files changed, 55 insertions(+), 2 deletions(-) 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 From 5b48e133376ddf79f56881ea16d002b3c7be3175 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 3 Sep 2019 14:25:10 +0200 Subject: [PATCH 2/7] Remove 'align face to bottom' from context-menu. part of CURA-6745 --- resources/qml/Actions.qml | 10 ---------- resources/qml/Menus/ContextMenu.qml | 1 - 2 files changed, 11 deletions(-) diff --git a/resources/qml/Actions.qml b/resources/qml/Actions.qml index 759d4e1785..7e6afa813d 100644 --- a/resources/qml/Actions.qml +++ b/resources/qml/Actions.qml @@ -26,7 +26,6 @@ Item property alias deleteSelection: deleteSelectionAction; property alias centerSelection: centerSelectionAction; - property alias bottomFaceSelection: bottomFaceSelectionAction; property alias multiplySelection: multiplySelectionAction; property alias deleteObject: deleteObjectAction; @@ -272,15 +271,6 @@ 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 5125c0c998..cb10d50ce8 100644 --- a/resources/qml/Menus/ContextMenu.qml +++ b/resources/qml/Menus/ContextMenu.qml @@ -20,7 +20,6 @@ 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 From 503a24f7a196262be9078892d7daedd5a444b725 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 3 Sep 2019 15:08:41 +0200 Subject: [PATCH 3/7] Make magic value into funtion (max face-id). part of CURA-6745 --- cura/CuraActions.py | 2 +- plugins/SolidView/SolidView.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cura/CuraActions.py b/cura/CuraActions.py index 0f2878023d..9338521003 100644 --- a/cura/CuraActions.py +++ b/cura/CuraActions.py @@ -85,7 +85,7 @@ class CuraActions(QObject): original_node, face_id = selected_face meshdata = original_node.getMeshDataTransformed() - if not meshdata or face_id < 0 or face_id > 0x10001: + if not meshdata or face_id < 0 or face_id > Selection.endFaceSelectionId(): return rotation_point, face_normal = meshdata.getFacePlane(face_id) diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py index 38bc5eada8..da18c328d1 100644 --- a/plugins/SolidView/SolidView.py +++ b/plugins/SolidView/SolidView.py @@ -140,9 +140,9 @@ class SolidView(View): 1.0 ] - # Color the currently selected face-id, 0x10001 is certain to be greater than the largest ID. + # Color the currently selected face-id. face = Selection.getSelectedFace() - uniforms["selected_face"] = 0x10001 if not face or node != face[0] else face[1] + uniforms["selected_face"] = Selection.endFaceSelectionId() if not face or node != face[0] else face[1] except ValueError: pass From 44db4216bcfa0b83dd464da9ad4d018fd6211600 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 3 Sep 2019 15:44:41 +0200 Subject: [PATCH 4/7] Rename to getEndFaceSelectionId() CURA-6745 --- cura/CuraActions.py | 2 +- plugins/SolidView/SolidView.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cura/CuraActions.py b/cura/CuraActions.py index 9338521003..1e992badc3 100644 --- a/cura/CuraActions.py +++ b/cura/CuraActions.py @@ -85,7 +85,7 @@ class CuraActions(QObject): original_node, face_id = selected_face meshdata = original_node.getMeshDataTransformed() - if not meshdata or face_id < 0 or face_id > Selection.endFaceSelectionId(): + if not meshdata or face_id < 0 or face_id > Selection.getEndFaceSelectionId(): return rotation_point, face_normal = meshdata.getFacePlane(face_id) diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py index da18c328d1..3f70a3612d 100644 --- a/plugins/SolidView/SolidView.py +++ b/plugins/SolidView/SolidView.py @@ -142,7 +142,7 @@ class SolidView(View): # Color the currently selected face-id. face = Selection.getSelectedFace() - uniforms["selected_face"] = Selection.endFaceSelectionId() if not face or node != face[0] else face[1] + uniforms["selected_face"] = Selection.getEndFaceSelectionId() if not face or node != face[0] else face[1] except ValueError: pass From efbcdcc8ac41d91ef9ea0fc93f718725f82fb2b1 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 3 Sep 2019 15:47:18 +0200 Subject: [PATCH 5/7] Add None check in bottomFaceSelection() CURA-6745 --- cura/CuraActions.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cura/CuraActions.py b/cura/CuraActions.py index 1e992badc3..8f60333261 100644 --- a/cura/CuraActions.py +++ b/cura/CuraActions.py @@ -3,7 +3,7 @@ from PyQt5.QtCore import QObject, QUrl from PyQt5.QtGui import QDesktopServices -from typing import List, cast +from typing import List, Optional, cast from UM.Event import CallFunctionEvent from UM.FlameProfiler import pyqtSlot @@ -94,12 +94,15 @@ class CuraActions(QObject): rotation_quaternion = Quaternion.rotationTo(face_normal_vector.normalized(), Vector(0.0, -1.0, 0.0)) operation = GroupedOperation() + current_node = None # type: Optional[SceneNode] 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() + if current_node is None: + return rotate_operation = RotateOperation(current_node, rotation_quaternion, rotation_point_vector) operation.addOperation(rotate_operation) From 70332978fde2883751765569a971a7f6ab68f684 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 3 Sep 2019 15:48:31 +0200 Subject: [PATCH 6/7] Less cryptic max face-id. part of CURA-6745 --- cura/CuraActions.py | 2 +- plugins/SolidView/SolidView.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cura/CuraActions.py b/cura/CuraActions.py index 9338521003..ad359973c1 100644 --- a/cura/CuraActions.py +++ b/cura/CuraActions.py @@ -85,7 +85,7 @@ class CuraActions(QObject): original_node, face_id = selected_face meshdata = original_node.getMeshDataTransformed() - if not meshdata or face_id < 0 or face_id > Selection.endFaceSelectionId(): + if not meshdata or face_id < 0 or face_id > Selection.maxFaceSelectionId(): return rotation_point, face_normal = meshdata.getFacePlane(face_id) diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py index da18c328d1..94691b637a 100644 --- a/plugins/SolidView/SolidView.py +++ b/plugins/SolidView/SolidView.py @@ -142,7 +142,7 @@ class SolidView(View): # Color the currently selected face-id. face = Selection.getSelectedFace() - uniforms["selected_face"] = Selection.endFaceSelectionId() if not face or node != face[0] else face[1] + uniforms["selected_face"] = (Selection.maxFaceSelectionId() + 1) if not face or node != face[0] else face[1] except ValueError: pass From a07e88125fe3c6a669663db1db292829b63c144a Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 3 Sep 2019 15:58:40 +0200 Subject: [PATCH 7/7] Fix typing CURA-6713 --- plugins/CuraProfileReader/CuraProfileReader.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/CuraProfileReader/CuraProfileReader.py b/plugins/CuraProfileReader/CuraProfileReader.py index 8ae7b7e0b0..5e2a4266c8 100644 --- a/plugins/CuraProfileReader/CuraProfileReader.py +++ b/plugins/CuraProfileReader/CuraProfileReader.py @@ -93,7 +93,11 @@ class CuraProfileReader(ProfileReader): Logger.log("e", "Error while trying to parse profile: %s", str(e)) return None - active_quality_definition = getMachineDefinitionIDForQualitySearch(CuraApplication.getInstance().getGlobalContainerStack().definition) + global_stack = CuraApplication.getInstance().getGlobalContainerStack() + if global_stack is None: + return None + + active_quality_definition = getMachineDefinitionIDForQualitySearch(global_stack.definition) if profile.getMetaDataEntry("definition") != active_quality_definition: profile.setMetaDataEntry("definition", active_quality_definition) return profile