From 40eedbcf70e48b7b66598f6b6f8f7eb480cd3b9a Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 10 Apr 2018 11:59:25 +0200 Subject: [PATCH] Add more info dialog CURA-5204 --- cura/CuraAppSignals.py | 9 ++ cura/CuraApplication.py | 7 + plugins/SliceInfoPlugin/MoreInfoWindow.qml | 158 +++++++++++++++++++++ plugins/SliceInfoPlugin/SliceInfo.py | 53 +++++-- plugins/SliceInfoPlugin/example_data.json | 101 +++++++++++++ resources/qml/Preferences/GeneralPage.qml | 18 ++- 6 files changed, 337 insertions(+), 9 deletions(-) create mode 100644 cura/CuraAppSignals.py create mode 100644 plugins/SliceInfoPlugin/MoreInfoWindow.qml create mode 100644 plugins/SliceInfoPlugin/example_data.json diff --git a/cura/CuraAppSignals.py b/cura/CuraAppSignals.py new file mode 100644 index 0000000000..b3cf97bb8b --- /dev/null +++ b/cura/CuraAppSignals.py @@ -0,0 +1,9 @@ +from PyQt5.QtCore import pyqtSignal, QObject + + +class CuraAppSignals(QObject): + + showMoreInfoOnAnonymousDataCollection = pyqtSignal() + + def __init__(self, parent = None): + super().__init__(parent) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index be44f020f2..38ee645a0d 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -74,6 +74,7 @@ from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager from cura.Machines.VariantManager import VariantManager +from .CuraAppSignals import CuraAppSignals from . import PlatformPhysics from . import BuildVolume from . import CameraAnimation @@ -645,6 +646,8 @@ class CuraApplication(QtApplication): def run(self): self.preRun() + self._app_signals = CuraAppSignals(self) + container_registry = ContainerRegistry.getInstance() Logger.log("i", "Initializing variant manager") @@ -780,6 +783,10 @@ class CuraApplication(QtApplication): def hasGui(self): return self._use_gui + @pyqtSlot(result = QObject) + def getCuraAppSignals(self, *args) -> CuraAppSignals: + return self._app_signals + @pyqtSlot(result = QObject) def getSettingVisibilityPresetsModel(self, *args) -> SettingVisibilityPresetsModel: return self._setting_visibility_presets_model diff --git a/plugins/SliceInfoPlugin/MoreInfoWindow.qml b/plugins/SliceInfoPlugin/MoreInfoWindow.qml new file mode 100644 index 0000000000..d453c18c7c --- /dev/null +++ b/plugins/SliceInfoPlugin/MoreInfoWindow.qml @@ -0,0 +1,158 @@ +// Copyright (c) 2018 Ultimaker B.V. +// PluginBrowser is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.7 +import QtQuick.Window 2.2 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 + +import UM 1.3 as UM +import Cura 1.0 as Cura + + +UM.Dialog +{ + id: baseDialog + title: catalog.i18nc("@title:window", "More information on anonymous data collection") + visible: false + + minimumWidth: 500 * screenScaleFactor + minimumHeight: 400 * screenScaleFactor + width: minimumWidth + height: minimumHeight + + property bool allowSendData: false // for saving the user's choice + + onAccepted: manager.setSendSliceInfo(allowSendData) + + onVisibilityChanged: + { + if (visible) + { + baseDialog.allowSendData = UM.Preferences.getValue("info/send_slice_info"); + if (baseDialog.allowSendData) + { + allowSendButton.checked = true; + } + else + { + dontSendButton.checked = true; + } + } + } + + Item + { + Connections + { + target: CuraApplication.getCuraAppSignals() + onShowMoreInfoOnAnonymousDataCollection: + { + baseDialog.show(); + } + } + + id: textRow + anchors + { + top: parent.top + bottom: radioButtonsRow.top + bottomMargin: UM.Theme.getSize("default_margin").height + left: parent.left + right: parent.right + } + + Label + { + id: headerText + anchors + { + top: parent.top + left: parent.left + right: parent.right + } + + text: catalog.i18nc("@text:window", "Cura sends anonymous data so we can improve the print quality and user experience. Below is an example of all the data we send.") + wrapMode: Text.WordWrap + } + + TextArea + { + id: exampleData + anchors + { + top: headerText.bottom + topMargin: UM.Theme.getSize("default_margin").height + bottom: parent.bottom + bottomMargin: UM.Theme.getSize("default_margin").height + left: parent.left + right: parent.right + } + + text: manager.getExampleData() + readOnly: true + textFormat: TextEdit.PlainText + } + } + + Column + { + id: radioButtonsRow + width: parent.width + anchors.bottom: buttonRow.top + anchors.bottomMargin: UM.Theme.getSize("default_margin").height + + ExclusiveGroup { id: group } + + RadioButton + { + id: dontSendButton + text: catalog.i18nc("@text:window", "I don't want to send these settings") + exclusiveGroup: group + onClicked: + { + baseDialog.allowSendData = !checked; + } + } + RadioButton + { + id: allowSendButton + text: catalog.i18nc("@text:window", "Allow sending these settings to improve Cura") + exclusiveGroup: group + onClicked: + { + baseDialog.allowSendData = checked; + } + } + } + + Item + { + id: buttonRow + anchors.bottom: parent.bottom + width: parent.width + anchors.bottomMargin: UM.Theme.getSize("default_margin").height + + UM.I18nCatalog { id: catalog; name: "cura" } + + Button + { + anchors.right: parent.right + text: catalog.i18nc("@action:button", "Ok") + onClicked: { + baseDialog.accepted() + baseDialog.hide() + } + } + + Button + { + anchors.left: parent.left + text: catalog.i18nc("@action:button", "Cancel") + onClicked: { + baseDialog.rejected() + baseDialog.hide() + } + } + } +} diff --git a/plugins/SliceInfoPlugin/SliceInfo.py b/plugins/SliceInfoPlugin/SliceInfo.py index 6991283bf5..5f1c2a25bc 100755 --- a/plugins/SliceInfoPlugin/SliceInfo.py +++ b/plugins/SliceInfoPlugin/SliceInfo.py @@ -1,10 +1,13 @@ -# Copyright (c) 2015 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. import json +import os import platform import time +from PyQt5.QtCore import pyqtSlot, QObject + from UM.Extension import Extension from UM.Application import Application from UM.Preferences import Preferences @@ -12,6 +15,7 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Message import Message from UM.i18n import i18nCatalog from UM.Logger import Logger +from UM.PluginRegistry import PluginRegistry from UM.Qt.Duration import DurationFormat from .SliceInfoJob import SliceInfoJob @@ -23,15 +27,19 @@ catalog = i18nCatalog("cura") ## This Extension runs in the background and sends several bits of information to the Ultimaker servers. # The data is only sent when the user in question gave permission to do so. All data is anonymous and # no model files are being sent (Just a SHA256 hash of the model). -class SliceInfo(Extension): +class SliceInfo(QObject, Extension): info_url = "https://stats.ultimaker.com/api/cura" - def __init__(self): - super().__init__() + def __init__(self, parent = None): + QObject.__init__(self, parent) + Extension.__init__(self) Application.getInstance().getOutputDeviceManager().writeStarted.connect(self._onWriteStarted) Preferences.getInstance().addPreference("info/send_slice_info", True) Preferences.getInstance().addPreference("info/asked_send_slice_info", False) + self._more_info_dialog = None + self._example_data_content = None + if not Preferences.getInstance().getValue("info/asked_send_slice_info"): self.send_slice_info_message = Message(catalog.i18nc("@info", "Cura collects anonymized usage statistics."), lifetime = 0, @@ -40,19 +48,48 @@ class SliceInfo(Extension): self.send_slice_info_message.addAction("Dismiss", name = catalog.i18nc("@action:button", "Allow"), icon = None, description = catalog.i18nc("@action:tooltip", "Allow Cura to send anonymized usage statistics to help prioritize future improvements to Cura. Some of your preferences and settings are sent, the Cura version and a hash of the models you're slicing.")) - self.send_slice_info_message.addAction("Disable", name = catalog.i18nc("@action:button", "Disable"), icon = None, - description = catalog.i18nc("@action:tooltip", "Don't allow Cura to send anonymized usage statistics. You can enable it again in the preferences."), button_style = Message.ActionButtonStyle.LINK) + self.send_slice_info_message.addAction("MoreInfo", name = catalog.i18nc("@action:button", "More info"), icon = None, + description = catalog.i18nc("@action:tooltip", "See more information on what data Cura sends."), button_style = Message.ActionButtonStyle.LINK) self.send_slice_info_message.actionTriggered.connect(self.messageActionTriggered) self.send_slice_info_message.show() + Application.getInstance().initializationFinished.connect(self._onAppInitialized) + + def _onAppInitialized(self): + if self._more_info_dialog is None: + self._more_info_dialog = self._createDialog("MoreInfoWindow.qml") + ## Perform action based on user input. # Note that clicking "Disable" won't actually disable the data sending, but rather take the user to preferences where they can disable it. def messageActionTriggered(self, message_id, action_id): Preferences.getInstance().setValue("info/asked_send_slice_info", True) - if action_id == "Disable": - Application.getInstance().showPreferences() + if action_id == "MoreInfo": + self._showMoreInfoDialog() self.send_slice_info_message.hide() + def _showMoreInfoDialog(self): + if self._more_info_dialog is None: + self._more_info_dialog = self._createDialog("MoreInfoWindow.qml") + self._more_info_dialog.open() + + def _createDialog(self, qml_name): + Logger.log("d", "Creating dialog [%s]", qml_name) + file_path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), qml_name) + dialog = Application.getInstance().createQmlComponent(file_path, {"manager": self}) + return dialog + + @pyqtSlot(result = str) + def getExampleData(self) -> str: + if self._example_data_content is None: + file_path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "example_data.json") + with open(file_path, "r", encoding = "utf-8") as f: + self._example_data_content = f.read() + return self._example_data_content + + @pyqtSlot(bool) + def setSendSliceInfo(self, enabled: bool): + Preferences.getInstance().setValue("info/send_slice_info", enabled) + def _onWriteStarted(self, output_device): try: if not Preferences.getInstance().getValue("info/send_slice_info"): diff --git a/plugins/SliceInfoPlugin/example_data.json b/plugins/SliceInfoPlugin/example_data.json new file mode 100644 index 0000000000..61cafbb27e --- /dev/null +++ b/plugins/SliceInfoPlugin/example_data.json @@ -0,0 +1,101 @@ +{ + "extruders": [ + { + "material": { + "GUID": "506c9f0d-e3aa-4bd4-b2d2-23e2425b1aa9", + "brand": "Generic", + "type": "PLA" + }, + "active": true, + "material_used": 4.82, + "nozzle_size": 0.4, + "variant": "AA 0.4", + "extruder_settings": { + "infill_pattern": "triangles", + "wall_line_count": 4, + "retraction_enable": true, + "infill_sparse_density": 0.0, + "gradual_infill_steps": 0.0, + "default_material_print_temperature": 200, + "material_print_temperature": 200 + } + }, + { + "material": { + "GUID": "86a89ceb-4159-47f6-ab97-e9953803d70f", + "brand": "Generic", + "type": "PVA" + }, + "active": false, + "material_used": 0.0, + "nozzle_size": 0.4, + "variant": "BB 0.4", + "extruder_settings": { + "infill_pattern": "triangles", + "wall_line_count": 3, + "retraction_enable": true, + "infill_sparse_density": 20, + "gradual_infill_steps": 0, + "default_material_print_temperature": 215, + "material_print_temperature": 215 + } + } + ], + "active_mode": "custom", + "schema_version": 0, + "print_times": { + "support": 0, + "infill": 0, + "total": 44548, + "travel": 5063 + }, + "machine_settings_changed_by_user": false, + "language": "en", + "cura_version": "2.7.0-master.20170721034526", + "os": { + "version": "10.0.14393", + "type": "Windows" + }, + "time_stamp": 1500629879.3985891, + "quality_profile": "normal", + "print_settings": { + "print_sequence": "all_at_once", + "infill_pattern": "triangles", + "layer_height": 0.1, + "infill_sparse_density": 20, + "wall_line_count": 3, + "retraction_enable": true, + "gradual_infill_steps": 0, + "adhesion_type": "none", + "prime_tower_enable": false, + "support_enabled": false, + "support_extruder_nr": 0 + }, + "models": [ + { + "extruder": 0, + "transformation": { + "data": "[[ 1.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [ 0.00000000e+00 8.96267878e-08 -9.99999881e-01 1.00000038e+01] [ 0.00000000e+00 9.99999881e-01 8.96267878e-08 1.77869296e-07] [ 0.00000000e+00 0.00000000e+00 0.00000000e+00 1.00000000e+00]]" + }, + "bounding_box": { + "minimum": { + "x": -80.0, + "y": -4.0903287974458635e-06, + "z": -101.49998861865569 + }, + "maximum": { + "x": 80.0, + "y": 20.000011181962602, + "z": 101.49998897439428 + } + }, + "model_settings": { + }, + "hash": "ca1a12e57ddf990fabc25d9105fc28eea266937c3f907d0e58edf9cc43f28551" + } + ], + "active_machine": { + "definition_id": "ultimaker3", + "manufacturer": "Ultimaker" + } +} diff --git a/resources/qml/Preferences/GeneralPage.qml b/resources/qml/Preferences/GeneralPage.qml index 1c7b2bcf7c..8e182ad473 100644 --- a/resources/qml/Preferences/GeneralPage.qml +++ b/resources/qml/Preferences/GeneralPage.qml @@ -649,7 +649,7 @@ UM.PreferencesPage UM.TooltipArea { visible: plugins.find("id", "SliceInfoPlugin") > -1 width: childrenRect.width - height: visible ? childrenRect.height : 0 + height: visible ? childrenRect.height * 2 : 0 text: catalog.i18nc("@info:tooltip","Should anonymous data about your print be sent to Ultimaker? Note, no models, IP addresses or other personally identifiable information is sent or stored.") CheckBox @@ -659,6 +659,22 @@ UM.PreferencesPage checked: boolCheck(UM.Preferences.getValue("info/send_slice_info")) onCheckedChanged: UM.Preferences.setValue("info/send_slice_info", checked) } + + Button + { + id: showMoreInfo + anchors + { + top: sendDataCheckbox.bottom + bottom: parent.bottom + } + + text: catalog.i18nc("@action:button", "Show more information on anonymous data collection") + onClicked: + { + CuraApplication.getCuraAppSignals().showMoreInfoOnAnonymousDataCollection(); + } + } } Item