diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 28c3ad26d4..2a0a3e4e7b 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -16,7 +16,7 @@ assignees: '' **Describe alternatives you've considered** -**Affected users and/or printers ** +**Affected users and/or printers** **Additional context** diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index a07c56ac6c..8e74a81842 100755 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -226,6 +226,8 @@ class BuildVolume(SceneNode): build_volume_bounding_box = self.getBoundingBox() if build_volume_bounding_box: # It's over 9000! + # We set this to a very low number, as we do allow models to intersect the build plate. + # This means the model gets cut off at the build plate. build_volume_bounding_box = build_volume_bounding_box.set(bottom=-9001) else: # No bounding box. This is triggered when running Cura from command line with a model for the first time @@ -245,7 +247,11 @@ class BuildVolume(SceneNode): if node.collidesWithArea(self.getDisallowedAreas()): node.setOutsideBuildArea(True) continue - + # If the entire node is below the build plate, still mark it as outside. + node_bounding_box = node.getBoundingBox() + if node_bounding_box and node_bounding_box.top < 0: + node.setOutsideBuildArea(True) + continue # Mark the node as outside build volume if the set extruder is disabled extruder_position = node.callDecoration("getActiveExtruderPosition") if extruder_position not in self._global_container_stack.extruders: diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index bec5d7975b..d41ad443e5 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -848,7 +848,6 @@ class CuraApplication(QtApplication): if diagonal < 1: #No printer added yet. Set a default camera distance for normal-sized printers. diagonal = 375 camera.setPosition(Vector(-80, 250, 700) * diagonal / 375) - camera.setPerspective(True) camera.lookAt(Vector(0, 0, 0)) controller.getScene().setActiveCamera("3d") diff --git a/cura/CuraView.py b/cura/CuraView.py index 45cd7ba61b..b358558dff 100644 --- a/cura/CuraView.py +++ b/cura/CuraView.py @@ -18,8 +18,8 @@ class CuraView(View): def __init__(self, parent = None, use_empty_menu_placeholder: bool = False) -> None: super().__init__(parent) - self._empty_menu_placeholder_url = QUrl(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, - "EmptyViewMenuComponent.qml")) + self._empty_menu_placeholder_url = QUrl.fromLocalFile(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, + "EmptyViewMenuComponent.qml")) self._use_empty_menu_placeholder = use_empty_menu_placeholder @pyqtProperty(QUrl, constant = True) diff --git a/cura/Scene/CuraSceneNode.py b/cura/Scene/CuraSceneNode.py index 1983bc6008..9038c12b2c 100644 --- a/cura/Scene/CuraSceneNode.py +++ b/cura/Scene/CuraSceneNode.py @@ -115,6 +115,9 @@ class CuraSceneNode(SceneNode): self._aabb = None if self._mesh_data: self._aabb = self._mesh_data.getExtents(self.getWorldTransformation()) + else: # If there is no mesh_data, use a boundingbox that encompasses the local (0,0,0) + position = self.getWorldPosition() + self._aabb = AxisAlignedBox(minimum=position, maximum=position) for child in self.getAllChildren(): if child.callDecoration("isNonPrintingMesh"): diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 03d0cf54e5..52906b7dbb 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -897,6 +897,8 @@ class MachineManager(QObject): continue old_value = container.getProperty(setting_key, "value") + if isinstance(old_value, SettingFunction): + old_value = old_value(self._global_container_stack) if int(old_value) < 0: continue if int(old_value) >= extruder_count or not self._global_container_stack.extruders[str(old_value)].isEnabled: diff --git a/cura/Snapshot.py b/cura/Snapshot.py index 0410d8670d..033b453684 100644 --- a/cura/Snapshot.py +++ b/cura/Snapshot.py @@ -48,7 +48,7 @@ class Snapshot: # determine zoom and look at bbox = None for node in DepthFirstIterator(root): - if hasattr(node, "_outside_buildarea") and not node._outside_buildarea: + if not getattr(node, "_outside_buildarea", False): if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible() and not node.callDecoration("isNonThumbnailVisibleMesh"): if bbox is None: bbox = node.getBoundingBox() @@ -66,7 +66,7 @@ class Snapshot: looking_from_offset = Vector(-1, 1, 2) if size > 0: # determine the watch distance depending on the size - looking_from_offset = looking_from_offset * size * 1.3 + looking_from_offset = looking_from_offset * size * 1.75 camera.setPosition(look_at + looking_from_offset) camera.lookAt(look_at) diff --git a/cura_app.py b/cura_app.py index 1978e0f5fd..3599f127cc 100755 --- a/cura_app.py +++ b/cura_app.py @@ -32,7 +32,8 @@ if not known_args["debug"]: elif Platform.isOSX(): return os.path.expanduser("~/Library/Logs/" + CuraAppName) - if hasattr(sys, "frozen"): + # Do not redirect stdout and stderr to files if we are running CLI. + if hasattr(sys, "frozen") and "cli" not in os.path.basename(sys.argv[0]).lower(): dirpath = get_cura_dir_path() os.makedirs(dirpath, exist_ok = True) sys.stdout = open(os.path.join(dirpath, "stdout.log"), "w", encoding = "utf-8") diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index a1460cca3f..ad10a4f075 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -104,7 +104,7 @@ class FirmwareUpdateCheckerJob(Job): # because the new version of Cura will be release before the firmware and we don't want to # notify the user when no new firmware version is available. if (checked_version != "") and (checked_version != current_version): - Logger.log("i", "SHOWING FIRMWARE UPDATE MESSAGE") + Logger.log("i", "Showing firmware update message for new version: {version}".format(current_version)) message = FirmwareUpdateCheckerMessage(machine_id, self._machine_name, self._lookups.getRedirectUserUrl()) message.actionTriggered.connect(self._callback) diff --git a/plugins/MachineSettingsAction/MachineSettingsPrinterTab.qml b/plugins/MachineSettingsAction/MachineSettingsPrinterTab.qml index 1535301616..2556eb3a9c 100644 --- a/plugins/MachineSettingsAction/MachineSettingsPrinterTab.qml +++ b/plugins/MachineSettingsAction/MachineSettingsPrinterTab.qml @@ -20,14 +20,14 @@ Item anchors.right: parent.right anchors.top: parent.top - property int labelWidth: 120 * screenScaleFactor - property int controlWidth: (UM.Theme.getSize("setting_control").width * 3 / 4) | 0 - property var labelFont: UM.Theme.getFont("default") - property int columnWidth: ((parent.width - 2 * UM.Theme.getSize("default_margin").width) / 2) | 0 property int columnSpacing: 3 * screenScaleFactor property int propertyStoreIndex: manager ? manager.storeContainerIndex : 1 // definition_changes + property int labelWidth: (columnWidth * 2 / 3 - UM.Theme.getSize("default_margin").width * 2) | 0 + property int controlWidth: (columnWidth / 3) | 0 + property var labelFont: UM.Theme.getFont("default") + property string machineStackId: Cura.MachineManager.activeMachineId property var forceUpdateFunction: manager.forceUpdate @@ -59,6 +59,8 @@ Item font: UM.Theme.getFont("medium_bold") color: UM.Theme.getColor("text") renderType: Text.NativeRendering + width: parent.width + elide: Text.ElideRight } Cura.NumericTextFieldWithUnit // "X (Width)" @@ -175,6 +177,8 @@ Item font: UM.Theme.getFont("medium_bold") color: UM.Theme.getColor("text") renderType: Text.NativeRendering + width: parent.width + elide: Text.ElideRight } Cura.PrintHeadMinMaxTextField // "X min" diff --git a/plugins/PostProcessingPlugin/scripts/Stretch.py b/plugins/PostProcessingPlugin/scripts/Stretch.py index 9757296041..13b41eaacd 100644 --- a/plugins/PostProcessingPlugin/scripts/Stretch.py +++ b/plugins/PostProcessingPlugin/scripts/Stretch.py @@ -145,6 +145,7 @@ class Stretcher(): current.readStep(line) onestep = GCodeStep(-1, in_relative_movement) onestep.copyPosFrom(current) + onestep.comment = line else: onestep = GCodeStep(-1, in_relative_movement) onestep.copyPosFrom(current) diff --git a/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml b/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml index fef2732af9..1773ef9053 100644 --- a/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml +++ b/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml @@ -89,6 +89,7 @@ Item Label { text: catalog.i18nc("@label", "Your rating") + ":" + visible: details.type == "plugin" font: UM.Theme.getFont("default") color: UM.Theme.getColor("text_medium") renderType: Text.NativeRendering diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index ebbb6a5a3c..aaf4977e7d 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -2784,6 +2784,19 @@ "settable_per_extruder": true, "limit_to_extruder": "adhesion_extruder_nr" }, + "speed_z_hop": + { + "label": "Z Hop Speed", + "description": "The speed at which the vertical Z movement is made for Z Hops. This is typically lower than the print speed since the build plate or machine's gantry is harder to move.", + "unit": "mm/s", + "type": "float", + "default_value": 10, + "minimum_value": "0", + "maximum_value": "machine_max_feedrate_z", + "enabled": "retraction_enable and retraction_hop_enabled", + "settable_per_mesh": false, + "settable_per_extruder": true + }, "max_feedrate_z_override": { "label": "Maximum Z Speed", diff --git a/resources/qml/Actions.qml b/resources/qml/Actions.qml index ce9618a560..71a655c664 100644 --- a/resources/qml/Actions.qml +++ b/resources/qml/Actions.qml @@ -101,7 +101,7 @@ Item Action { id: redoAction; - text: catalog.i18nc("@action:inmenu menubar:edit","&Redo"); + text: catalog.i18nc("@action:inmenu menubar:edit", "&Redo"); iconName: "edit-redo"; shortcut: StandardKey.Redo; onTriggered: UM.OperationStack.redo(); @@ -110,65 +110,65 @@ Item Action { - id: quitAction; - text: catalog.i18nc("@action:inmenu menubar:file","&Quit"); - iconName: "application-exit"; - shortcut: StandardKey.Quit; + id: quitAction + text: catalog.i18nc("@action:inmenu menubar:file","&Quit") + iconName: "application-exit" + shortcut: StandardKey.Quit } Action { - id: view3DCameraAction; - text: catalog.i18nc("@action:inmenu menubar:view","3D View"); - onTriggered: UM.Controller.rotateView("3d", 0); + id: view3DCameraAction + text: catalog.i18nc("@action:inmenu menubar:view", "3D View") + onTriggered: UM.Controller.setCameraRotation("3d", 0) } Action { - id: viewFrontCameraAction; - text: catalog.i18nc("@action:inmenu menubar:view","Front View"); - onTriggered: UM.Controller.rotateView("home", 0); + id: viewFrontCameraAction + text: catalog.i18nc("@action:inmenu menubar:view", "Front View") + onTriggered: UM.Controller.setCameraRotation("home", 0) } Action { - id: viewTopCameraAction; - text: catalog.i18nc("@action:inmenu menubar:view","Top View"); - onTriggered: UM.Controller.rotateView("y", 90); + id: viewTopCameraAction + text: catalog.i18nc("@action:inmenu menubar:view", "Top View") + onTriggered: UM.Controller.setCameraRotation("y", 90) } Action { - id: viewLeftSideCameraAction; - text: catalog.i18nc("@action:inmenu menubar:view","Left Side View"); - onTriggered: UM.Controller.rotateView("x", 90); + id: viewLeftSideCameraAction + text: catalog.i18nc("@action:inmenu menubar:view", "Left Side View") + onTriggered: UM.Controller.setCameraRotation("x", 90) } Action { - id: viewRightSideCameraAction; - text: catalog.i18nc("@action:inmenu menubar:view","Right Side View"); - onTriggered: UM.Controller.rotateView("x", -90); + id: viewRightSideCameraAction + text: catalog.i18nc("@action:inmenu menubar:view", "Right Side View") + onTriggered: UM.Controller.setCameraRotation("x", -90) } Action { - id: preferencesAction; - text: catalog.i18nc("@action:inmenu","Configure Cura..."); - iconName: "configure"; + id: preferencesAction + text: catalog.i18nc("@action:inmenu", "Configure Cura...") + iconName: "configure" } Action { - id: addMachineAction; - text: catalog.i18nc("@action:inmenu menubar:printer","&Add Printer..."); + id: addMachineAction + text: catalog.i18nc("@action:inmenu menubar:printer", "&Add Printer...") } Action { - id: settingsAction; - text: catalog.i18nc("@action:inmenu menubar:printer","Manage Pr&inters..."); - iconName: "configure"; + id: settingsAction + text: catalog.i18nc("@action:inmenu menubar:printer", "Manage Pr&inters...") + iconName: "configure" } Action diff --git a/resources/qml/Preferences/GeneralPage.qml b/resources/qml/Preferences/GeneralPage.qml index 47cc11632c..dd6004eae0 100644 --- a/resources/qml/Preferences/GeneralPage.qml +++ b/resources/qml/Preferences/GeneralPage.qml @@ -95,6 +95,10 @@ UM.PreferencesPage UM.Preferences.resetPreference("view/top_layer_count"); topLayerCountCheckbox.checked = boolCheck(UM.Preferences.getValue("view/top_layer_count")) + UM.Preferences.resetPreference("general/camera_perspective_mode") + var defaultCameraMode = UM.Preferences.getValue("general/camera_perspective_mode") + setDefaultCameraMode(defaultCameraMode) + UM.Preferences.resetPreference("cura/choice_on_profile_override") setDefaultDiscardOrKeepProfile(UM.Preferences.getValue("cura/choice_on_profile_override")) @@ -330,7 +334,8 @@ UM.PreferencesPage } } - UM.TooltipArea { + UM.TooltipArea + { width: childrenRect.width; height: childrenRect.height; text: catalog.i18nc("@info:tooltip", "Moves the camera so the model is in the center of the view when a model is selected") @@ -344,7 +349,8 @@ UM.PreferencesPage } } - UM.TooltipArea { + UM.TooltipArea + { width: childrenRect.width; height: childrenRect.height; text: catalog.i18nc("@info:tooltip", "Should the default zoom behavior of cura be inverted?") @@ -436,6 +442,50 @@ UM.PreferencesPage } } + UM.TooltipArea + { + width: childrenRect.width + height: childrenRect.height + text: catalog.i18nc("@info:tooltip", "What type of camera rendering should be used?") + Column + { + spacing: 4 * screenScaleFactor + + Label + { + text: catalog.i18nc("@window:text", "Camera rendering: ") + } + ComboBox + { + id: cameraComboBox + + model: ListModel + { + id: comboBoxList + + Component.onCompleted: { + append({ text: catalog.i18n("Perspective"), code: "perspective" }) + append({ text: catalog.i18n("Orthogonal"), code: "orthogonal" }) + } + } + + currentIndex: + { + var code = UM.Preferences.getValue("general/camera_perspective_mode"); + for(var i = 0; i < comboBoxList.count; ++i) + { + if(model.get(i).code == code) + { + return i + } + } + return 0 + } + onActivated: UM.Preferences.setValue("general/camera_perspective_mode", model.get(index).code) + } + } + } + Item { //: Spacer diff --git a/resources/qml/Settings/SettingCheckBox.qml b/resources/qml/Settings/SettingCheckBox.qml index 0c7321d08a..8c0c58f371 100644 --- a/resources/qml/Settings/SettingCheckBox.qml +++ b/resources/qml/Settings/SettingCheckBox.qml @@ -29,7 +29,7 @@ SettingItem // 4: variant // 5: machine var value - if ((base.resolve != "None") && (stackLevel != 0) && (stackLevel != 1)) + if ((base.resolve !== undefined && base.resolve != "None") && (stackLevel != 0) && (stackLevel != 1)) { // We have a resolve function. Indicates that the setting is not settable per extruder and that // we have to choose between the resolved value (default) and the global value diff --git a/resources/qml/Settings/SettingComboBox.qml b/resources/qml/Settings/SettingComboBox.qml index 37df0bd9b9..6fcc1951a4 100644 --- a/resources/qml/Settings/SettingComboBox.qml +++ b/resources/qml/Settings/SettingComboBox.qml @@ -54,7 +54,7 @@ SettingItem { // FIXME this needs to go away once 'resolve' is combined with 'value' in our data model. var value = undefined - if ((base.resolve != "None") && (base.stackLevel != 0) && (base.stackLevel != 1)) + if ((base.resolve !== undefined && base.resolve != "None") && (base.stackLevel != 0) && (base.stackLevel != 1)) { // We have a resolve function. Indicates that the setting is not settable per extruder and that // we have to choose between the resolved value (default) and the global value diff --git a/resources/qml/Settings/SettingItem.qml b/resources/qml/Settings/SettingItem.qml index a95c888176..04b601f983 100644 --- a/resources/qml/Settings/SettingItem.qml +++ b/resources/qml/Settings/SettingItem.qml @@ -36,6 +36,20 @@ Item property var resolve: Cura.MachineManager.activeStackId !== Cura.MachineManager.activeMachineId ? propertyProvider.properties.resolve : "None" property var stackLevels: propertyProvider.stackLevels property var stackLevel: stackLevels[0] + // A list of stack levels that will trigger to show the revert button + property var showRevertStackLevels: [0] + property bool resetButtonVisible: { + var is_revert_stack_level = false; + for (var i in base.showRevertStackLevels) + { + if (base.stackLevel == i) + { + is_revert_stack_level = true + break + } + } + return is_revert_stack_level && base.showRevertButton + } signal focusReceived() signal setActiveFocusToNextSetting(bool forward) @@ -184,7 +198,7 @@ Item { id: revertButton - visible: base.stackLevel == 0 && base.showRevertButton + visible: base.resetButtonVisible height: parent.height width: height diff --git a/resources/qml/Settings/SettingView.qml b/resources/qml/Settings/SettingView.qml index 848dc7d5cb..9c964347ca 100644 --- a/resources/qml/Settings/SettingView.qml +++ b/resources/qml/Settings/SettingView.qml @@ -512,12 +512,7 @@ Item text: catalog.i18nc("@action:menu", "Hide this setting"); onTriggered: { - definitionsModel.hide(contextMenu.key); - // visible settings have changed, so we're no longer showing a preset - if (settingVisibilityPresetsModel.activePreset != "") - { - settingVisibilityPresetsModel.setActivePreset("custom"); - } + definitionsModel.hide(contextMenu.key) } } MenuItem @@ -545,11 +540,6 @@ Item { definitionsModel.show(contextMenu.key); } - // visible settings have changed, so we're no longer showing a preset - if (settingVisibilityPresetsModel.activePreset != "") - { - settingVisibilityPresetsModel.setActivePreset("custom"); - } } } MenuItem diff --git a/resources/qml/ViewOrientationControls.qml b/resources/qml/ViewOrientationControls.qml index 51ed6e3dcb..5750e935f4 100644 --- a/resources/qml/ViewOrientationControls.qml +++ b/resources/qml/ViewOrientationControls.qml @@ -6,7 +6,7 @@ import QtQuick.Controls 1.1 import QtQuick.Controls.Styles 1.1 import UM 1.4 as UM - +import Cura 1.1 as Cura // A row of buttons that control the view direction Row { @@ -19,30 +19,30 @@ Row ViewOrientationButton { iconSource: UM.Theme.getIcon("view_3d") - onClicked: UM.Controller.rotateView("3d", 0) + onClicked: Cura.Actions.view3DCamera.trigger() } ViewOrientationButton { iconSource: UM.Theme.getIcon("view_front") - onClicked: UM.Controller.rotateView("home", 0) + onClicked: Cura.Actions.viewFrontCamera.trigger() } ViewOrientationButton { iconSource: UM.Theme.getIcon("view_top") - onClicked: UM.Controller.rotateView("y", 90) + onClicked: Cura.Actions.viewTopCamera.trigger() } ViewOrientationButton { iconSource: UM.Theme.getIcon("view_left") - onClicked: UM.Controller.rotateView("x", 90) + onClicked: Cura.Actions.viewLeftSideCamera.trigger() } ViewOrientationButton { iconSource: UM.Theme.getIcon("view_right") - onClicked: UM.Controller.rotateView("x", -90) + onClicked: Cura.Actions.viewRightSideCamera.trigger() } } diff --git a/tests/TestOAuth2.py b/tests/TestOAuth2.py index fa0639e65a..358ed5afbb 100644 --- a/tests/TestOAuth2.py +++ b/tests/TestOAuth2.py @@ -101,7 +101,7 @@ def test_initialize(): initialize_preferences = MagicMock() authorization_service = AuthorizationService(OAUTH_SETTINGS, original_preference) authorization_service.initialize(initialize_preferences) - assert initialize_preferences.addPreference.called + initialize_preferences.addPreference.assert_called_once_with("test/auth_data", "{}") original_preference.addPreference.assert_not_called()