diff --git a/PrintInformation.py b/PrintInformation.py new file mode 100644 index 0000000000..668cade646 --- /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) + 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) + 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: