diff --git a/PrintInformation.py b/PrintInformation.py new file mode 100644 index 0000000000..1acadd41a6 --- /dev/null +++ b/PrintInformation.py @@ -0,0 +1,190 @@ +from PyQt5.QtCore import QObject, QDateTime, QTimer, pyqtSignal, pyqtSlot, pyqtProperty + +from UM.Application import Application +from UM.Settings.MachineSettings import MachineSettings +from UM.Resources import Resources +from UM.Scene.SceneNode import SceneNode + +## A class for processing and calculating minimum, currrent and maximum print time. +# +# This class contains all the logic relating to calculation and slicing for the +# time/quality slider concept. It is a rather tricky combination of event handling +# and state management. The logic behind this is as follows: +# +# - A scene change or settting change event happens. +# We track what the source was of the change, either a scene change, a setting change, an active machine change or something else. +# - This triggers a new slice with the current settings - this is the "current settings pass". +# - When the slice is done, we update the current print time and material amount. +# - If the source of the slice was not a Setting change, we start the second slice pass, the "low quality settings pass". Otherwise we stop here. +# - When that is done, we update the minimum print time and start the final slcice pass, the "high quality settings pass". +# - When the high quality pass is done, we update the maximum print time. +# +class PrintInformation(QObject): + class SlicePass: + CurrentSettings = 1 + LowQualitySettings = 2 + HighQualitySettings = 3 + + class SliceReason: + SceneChanged = 1 + SettingChanged = 2 + ActiveMachineChanged = 3 + Other = 4 + + def __init__(self, parent = None): + super().__init__(parent) + + self._minimum_print_time = QDateTime() + self._current_print_time = QDateTime() + self._maximum_print_time = QDateTime() + + self._material_amount = -1 + + self._time_quality_value = 50 + self._time_quality_changed_timer = QTimer() + self._time_quality_changed_timer.setInterval(500) + self._time_quality_changed_timer.setSingleShot(True) + self._time_quality_changed_timer.timeout.connect(self._updateTimeQualitySettings) + + self._interpolation_settings = { + "layer_height": { "minimum": "low", "maximum": "high", "curve": "linear" } + } + + self._low_quality_settings = None + self._current_settings = None + self._high_quality_settings = None + + self._slice_pass = None + self._slice_reason = None + + Application.getInstance().activeMachineChanged.connect(self._onActiveMachineChanged) + self._onActiveMachineChanged() + + Application.getInstance().getController().getScene().sceneChanged.connect(self._onSceneChanged) + + self._backend = Application.getInstance().getBackend() + if self._backend: + self._backend.printDurationMessage.connect(self._onPrintDurationMessage) + self._backend.slicingStarted.connect(self._onSlicingStarted) + + minimumPrintTimeChanged = pyqtSignal() + @pyqtProperty(QDateTime, notify = minimumPrintTimeChanged) + def minimumPrintTime(self): + return self._minimum_print_time + + currentPrintTimeChanged = pyqtSignal() + @pyqtProperty(QDateTime, notify = currentPrintTimeChanged) + def currentPrintTime(self): + return self._current_print_time + + maximumPrintTimeChanged = pyqtSignal() + @pyqtProperty(QDateTime, notify = maximumPrintTimeChanged) + def maximumPrintTime(self): + return self._maximum_print_time + + materialAmountChanged = pyqtSignal() + @pyqtProperty(float, notify = materialAmountChanged) + def materialAmount(self): + return self._material_amount + + timeQualityValueChanged = pyqtSignal() + @pyqtProperty(int, notify = timeQualityValueChanged) + def timeQualityValue(self): + return self._time_quality_value + + @pyqtSlot(int) + def setTimeQualityValue(self, value): + if value != self._time_quality_value: + self._time_quality_value = value + self.timeQualityValueChanged.emit() + + self._time_quality_changed_timer.start() + + def _onSlicingStarted(self): + if self._slice_pass is None: + self._slice_pass = self.SlicePass.CurrentSettings + + if self._slice_reason is None: + self._slice_reason = self.SliceReason.Other + + def _onPrintDurationMessage(self, time, amount): + if self._slice_pass == self.SlicePass.CurrentSettings: + self._current_print_time = QDateTime.fromMSecsSinceEpoch(round(time * 1000)) + self.currentPrintTimeChanged.emit() + + self._material_amount = round(amount / 10) / 100 + self.materialAmountChanged.emit() + + if self._slice_reason != self.SliceReason.SettingChanged: + self._slice_pass = self.SlicePass.LowQualitySettings + self._backend.slice(settings = self._low_quality_settings, save_gcode = False, save_polygons = False, force_restart = False, report_progress = False) + else: + self._slice_pass = None + self._slice_reason = None + + elif self._slice_pass == self.SlicePass.LowQualitySettings: + self._minimum_print_time = QDateTime.fromMSecsSinceEpoch(round(time * 1000)) + self.minimumPrintTimeChanged.emit() + + self._slice_pass = self.SlicePass.HighQualitySettings + self._backend.slice(settings = self._high_quality_settings, save_gcode = False, save_polygons = False, force_restart = False, report_progress = False) + elif self._slice_pass == self.SlicePass.HighQualitySettings: + self._maximum_print_time = QDateTime.fromMSecsSinceEpoch(round(time * 1000)) + self.maximumPrintTimeChanged.emit() + + self._slice_pass = None + self._slice_reason = None + + def _onActiveMachineChanged(self): + if self._current_settings: + self._current_settings.settingChanged.disconnect(self._onSettingChanged) + + self._current_settings = Application.getInstance().getActiveMachine() + + if self._current_settings: + self._current_settings.settingChanged.connect(self._onSettingChanged) + self._low_quality_settings = None + self._high_quality_settings = None + self._updateTimeQualitySettings() + + self._slice_reason = self.SliceReason.ActiveMachineChanged + + def _updateTimeQualitySettings(self): + if not self._current_settings: + return + + if not self._low_quality_settings: + self._low_quality_settings = MachineSettings() + self._low_quality_settings.loadSettingsFromFile(Resources.getPath(Resources.SettingsLocation, self._current_settings.getTypeID() + '.json')) + self._low_quality_settings.loadValuesFromFile(Resources.getPath(Resources.SettingsLocation, 'profiles', 'low_quality.conf')) + + if not self._high_quality_settings: + self._high_quality_settings = MachineSettings() + self._high_quality_settings.loadSettingsFromFile(Resources.getPath(Resources.SettingsLocation, self._current_settings.getTypeID() + '.json')) + self._high_quality_settings.loadValuesFromFile(Resources.getPath(Resources.SettingsLocation, 'profiles', 'high_quality.conf')) + + for key, options in self._interpolation_settings.items(): + minimum_value = None + if options['minimum'] == 'low': + minimum_value = self._low_quality_settings.getSettingValueByKey(key) + elif options['minimum'] == 'high': + minimum_value = self._high_quality_settings.getSettingValueByKey(key) + else: + continue + + maximum_value = None + if options['maximum'] == 'low': + maximum_value = self._low_quality_settings.getSettingValueByKey(key) + elif options['maximum'] == 'high': + maximum_value = self._high_quality_settings.getSettingValueByKey(key) + else: + continue + + setting_value = minimum_value + (maximum_value - minimum_value) * (self._time_quality_value / 100) + self._current_settings.setSettingValueByKey(key, setting_value) + + def _onSceneChanged(self, source): + self._slice_reason = self.SliceReason.SceneChanged + + def _onSettingChanged(self, source): + self._slice_reason = self.SliceReason.SettingChanged diff --git a/PrinterApplication.py b/PrinterApplication.py index 1c1136174a..5bca8e1d1a 100644 --- a/PrinterApplication.py +++ b/PrinterApplication.py @@ -23,6 +23,7 @@ from UM.Operations.SetTransformOperation import SetTransformOperation from PlatformPhysics import PlatformPhysics from BuildVolume import BuildVolume from CameraAnimation import CameraAnimation +from PrintInformation import PrintInformation from PyQt5.QtCore import pyqtSlot, QUrl, Qt, pyqtSignal, pyqtProperty from PyQt5.QtGui import QColor @@ -56,8 +57,7 @@ class PrinterApplication(QtApplication): 'priority': 0 } } - self._print_duration = -1 - self._print_material_amount = -1 + self._print_information = None self.activeMachineChanged.connect(self._onActiveMachineChanged) @@ -117,8 +117,6 @@ class PrinterApplication(QtApplication): self.getStorageDevice('LocalFileStorage').removableDrivesChanged.connect(self._removableDrivesChanged) - self.getBackend().printDurationMessage.connect(self._onPrintDurationMessage) - if self.getMachines(): active_machine_pref = Preferences.getInstance().getValue('cura/active_machine') if active_machine_pref: @@ -139,6 +137,8 @@ class PrinterApplication(QtApplication): def registerObjects(self, engine): engine.rootContext().setContextProperty('Printer', self) + self._print_information = PrintInformation() + engine.rootContext().setContextProperty('PrintInformation', self._print_information) def onSelectionChanged(self): if Selection.hasSelection(): @@ -263,16 +263,6 @@ class PrinterApplication(QtApplication): return log - printDurationChanged = pyqtSignal() - - @pyqtProperty(float, notify = printDurationChanged) - def printDuration(self): - return self._print_duration - - @pyqtProperty(float, notify = printDurationChanged) - def printMaterialAmount(self): - return self._print_material_amount - def _onActiveMachineChanged(self): machine = self.getActiveMachine() if machine: diff --git a/qml/SaveButton.qml b/qml/SaveButton.qml index d6912e53a4..263051db0d 100644 --- a/qml/SaveButton.qml +++ b/qml/SaveButton.qml @@ -19,9 +19,8 @@ Button { property bool defaultOverride: false; property bool defaultAmbiguous: false; - property real printDurationHours: Math.floor(Printer.printDuration / 3600); - property real printDurationMinutes: Math.round(((Printer.printDuration / 3600) - printDurationHours) * 60); - property real printMaterialAmount: Printer.printMaterialAmount < 0 ? -1 : Math.round(Printer.printMaterialAmount / 10) / 100; + property variant printDuration: PrintInformation.currentPrintTime; + property real printMaterialAmount: PrintInformation.materialAmount < 0 ? -1 : PrintInformation.materialAmount; iconSource: UM.Theme.icons[Printer.outputDevices[base.currentDevice].icon]; tooltip: Printer.outputDevices[base.currentDevice].description; @@ -138,23 +137,31 @@ Button { font: UM.Theme.fonts.default; text: { - if(control.printDurationHours < 0 || control.printDurationMinutes < 0) + if(!control.printDuration) { return ""; } - if(control.printDurationHours > 1) + var days = "" + var day_count = control.printDuration.getDate() - 1; + if(day_count > 0) { - return qsTr("%1 hours %2 minutes").arg(control.printDurationHours).arg(control.printDurationMinutes); + days = Qt.formatDateTime(control.printDuration, qsTr("d 'day(s)'", "", day_count)); } - else if(control.printDurationHours > 0) + + var hours = "" + if(control.printDuration.getHours() > 0) { - return qsTr("1 hour %1 minutes").arg(control.printDurationMinutes); + hours = Qt.formatDateTime(control.printDuration, qsTr("h 'hour(s)'", "", control.printDuration.getHours())); } - else + + var minutes = "" + if(control.printDuration.getMinutes() > 0) { - return qsTr("%2 minutes").arg(control.printDurationMinutes); + minutes = Qt.formatDateTime(control.printDuration, qsTr("m 'minute(s)'", "", control.printDuration.getMinutes())); } + + return [days, hours, minutes].join(" "); } } Label { diff --git a/qml/SidebarSimple.qml b/qml/SidebarSimple.qml index 53dbb5a48a..874ef90e8d 100644 --- a/qml/SidebarSimple.qml +++ b/qml/SidebarSimple.qml @@ -6,12 +6,17 @@ import QtQuick.Layouts 1.1 import UM 1.0 as UM Item { + id: base; + anchors.fill: parent; anchors.leftMargin: UM.Theme.sizes.default_margin.width; anchors.rightMargin: UM.Theme.sizes.default_margin.width; property Action configureSettings; + property variant minimumPrintTime: PrintInformation.minimumPrintTime; + property variant maximumPrintTime: PrintInformation.maximumPrintTime; + ColumnLayout { anchors.fill: parent; @@ -22,15 +27,33 @@ Item { Label { anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter; - text: "00:00"; + text:Qt.formatTime(base.minimumPrintTime, "hh:mm"); font: UM.Theme.fonts.timeslider_time; color: UM.Theme.colors.primary; } - Label { anchors.centerIn: parent; text: "Estimated Print Time"; color: UM.Theme.colors.text; font: UM.Theme.fonts.default; } + Label { + anchors.centerIn: parent; + text: { + if (UM.Backend.progress < 0) + { + return qsTr("No Model Loaded"); + } + else if (base.minimumPrintTime == null || base.maximumPrintTime == null) + { + return qsTr("Calculating...") + } + else + { + return qsTr("Estimated Print Time"); + } + } + color: UM.Theme.colors.text; + font: UM.Theme.fonts.default; + } Label { anchors.right: parent.right; anchors.verticalCenter: parent.verticalCenter; - text: "21:00"; + text: Qt.formatTime(base.maximumPrintTime, "hh:mm"); font: UM.Theme.fonts.timeslider_time; color: UM.Theme.colors.primary; } @@ -43,6 +66,9 @@ Item { minimumValue: 0; maximumValue: 100; + value: PrintInformation.timeQualityValue; + onValueChanged: PrintInformation.setTimeQualityValue(value); + style: UM.Theme.styles.slider; }