PrintInformation.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. # Copyright (c) 2015 Ultimaker B.V.
  2. # Cura is released under the terms of the AGPLv3 or higher.
  3. from PyQt5.QtCore import QObject, QDateTime, QTimer, pyqtSignal, pyqtSlot, pyqtProperty
  4. from UM.Application import Application
  5. from UM.Settings.MachineSettings import MachineSettings
  6. from UM.Resources import Resources
  7. from UM.Scene.SceneNode import SceneNode
  8. from UM.Qt.Duration import Duration
  9. import math
  10. ## A class for processing and calculating minimum, currrent and maximum print time.
  11. #
  12. # This class contains all the logic relating to calculation and slicing for the
  13. # time/quality slider concept. It is a rather tricky combination of event handling
  14. # and state management. The logic behind this is as follows:
  15. #
  16. # - A scene change or settting change event happens.
  17. # We track what the source was of the change, either a scene change, a setting change, an active machine change or something else.
  18. # - This triggers a new slice with the current settings - this is the "current settings pass".
  19. # - When the slice is done, we update the current print time and material amount.
  20. # - 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.
  21. # - When that is done, we update the minimum print time and start the final slcice pass, the "high quality settings pass".
  22. # - When the high quality pass is done, we update the maximum print time.
  23. #
  24. class PrintInformation(QObject):
  25. class SlicePass:
  26. CurrentSettings = 1
  27. LowQualitySettings = 2
  28. HighQualitySettings = 3
  29. class SliceReason:
  30. SceneChanged = 1
  31. SettingChanged = 2
  32. ActiveMachineChanged = 3
  33. Other = 4
  34. def __init__(self, parent = None):
  35. super().__init__(parent)
  36. self._enabled = False
  37. self._minimum_print_time = Duration(None, self)
  38. self._current_print_time = Duration(None, self)
  39. self._maximum_print_time = Duration(None, self)
  40. self._material_amount = -1
  41. self._time_quality_value = 50
  42. self._time_quality_changed_timer = QTimer()
  43. self._time_quality_changed_timer.setInterval(500)
  44. self._time_quality_changed_timer.setSingleShot(True)
  45. self._time_quality_changed_timer.timeout.connect(self._updateTimeQualitySettings)
  46. self._interpolation_settings = {
  47. "layer_height": { "minimum": "low", "maximum": "high", "curve": "linear", "precision": 2 },
  48. "fill_sparse_density": { "minimum": "low", "maximum": "high", "curve": "linear", "precision": 0 }
  49. }
  50. self._low_quality_settings = None
  51. self._current_settings = None
  52. self._high_quality_settings = None
  53. self._slice_pass = None
  54. self._slice_reason = None
  55. Application.getInstance().activeMachineChanged.connect(self._onActiveMachineChanged)
  56. self._onActiveMachineChanged()
  57. Application.getInstance().getController().getScene().sceneChanged.connect(self._onSceneChanged)
  58. self._backend = Application.getInstance().getBackend()
  59. if self._backend:
  60. self._backend.printDurationMessage.connect(self._onPrintDurationMessage)
  61. self._backend.slicingStarted.connect(self._onSlicingStarted)
  62. self._backend.slicingCancelled.connect(self._onSlicingCancelled)
  63. minimumPrintTimeChanged = pyqtSignal()
  64. @pyqtProperty(Duration, notify = minimumPrintTimeChanged)
  65. def minimumPrintTime(self):
  66. return self._minimum_print_time
  67. currentPrintTimeChanged = pyqtSignal()
  68. @pyqtProperty(Duration, notify = currentPrintTimeChanged)
  69. def currentPrintTime(self):
  70. return self._current_print_time
  71. maximumPrintTimeChanged = pyqtSignal()
  72. @pyqtProperty(Duration, notify = maximumPrintTimeChanged)
  73. def maximumPrintTime(self):
  74. return self._maximum_print_time
  75. materialAmountChanged = pyqtSignal()
  76. @pyqtProperty(float, notify = materialAmountChanged)
  77. def materialAmount(self):
  78. return self._material_amount
  79. timeQualityValueChanged = pyqtSignal()
  80. @pyqtProperty(int, notify = timeQualityValueChanged)
  81. def timeQualityValue(self):
  82. return self._time_quality_value
  83. def setEnabled(self, enabled):
  84. if enabled != self._enabled:
  85. self._enabled = enabled
  86. if self._enabled:
  87. self._updateTimeQualitySettings()
  88. self._onSlicingStarted()
  89. self.enabledChanged.emit()
  90. enabledChanged = pyqtSignal()
  91. @pyqtProperty(bool, fset = setEnabled, notify = enabledChanged)
  92. def enabled(self):
  93. return self._enabled
  94. @pyqtSlot(int)
  95. def setTimeQualityValue(self, value):
  96. if value != self._time_quality_value:
  97. self._time_quality_value = value
  98. self.timeQualityValueChanged.emit()
  99. self._time_quality_changed_timer.start()
  100. def _onSlicingStarted(self):
  101. if self._slice_pass is None:
  102. self._slice_pass = self.SlicePass.CurrentSettings
  103. if self._slice_reason is None:
  104. self._slice_reason = self.SliceReason.Other
  105. if self._slice_pass == self.SlicePass.CurrentSettings and self._slice_reason != self.SliceReason.SettingChanged:
  106. self._minimum_print_time.setDuration(-1)
  107. self.minimumPrintTimeChanged.emit()
  108. self._maximum_print_time.setDuration(-1)
  109. self.maximumPrintTimeChanged.emit()
  110. def _onPrintDurationMessage(self, time, amount):
  111. if self._slice_pass == self.SlicePass.CurrentSettings:
  112. self._current_print_time.setDuration(time)
  113. self.currentPrintTimeChanged.emit()
  114. # Material amount is sent as an amount of mm^3, so calculate length from that
  115. r = self._current_settings.getSettingValueByKey("material_diameter") / 2
  116. self._material_amount = round((amount / (math.pi * r ** 2)) / 1000, 2)
  117. self.materialAmountChanged.emit()
  118. if not self._enabled:
  119. return
  120. if self._slice_reason != self.SliceReason.SettingChanged or not self._minimum_print_time.valid or not self._maximum_print_time.valid:
  121. self._slice_pass = self.SlicePass.LowQualitySettings
  122. self._backend.slice(settings = self._low_quality_settings, save_gcode = False, save_polygons = False, force_restart = False, report_progress = False)
  123. else:
  124. self._slice_pass = None
  125. self._slice_reason = None
  126. elif self._slice_pass == self.SlicePass.LowQualitySettings:
  127. self._minimum_print_time.setDuration(time)
  128. self.minimumPrintTimeChanged.emit()
  129. self._slice_pass = self.SlicePass.HighQualitySettings
  130. self._backend.slice(settings = self._high_quality_settings, save_gcode = False, save_polygons = False, force_restart = False, report_progress = False)
  131. elif self._slice_pass == self.SlicePass.HighQualitySettings:
  132. self._maximum_print_time.setDuration(time)
  133. self.maximumPrintTimeChanged.emit()
  134. self._slice_pass = None
  135. self._slice_reason = None
  136. def _onActiveMachineChanged(self):
  137. if self._current_settings:
  138. self._current_settings.settingChanged.disconnect(self._onSettingChanged)
  139. self._current_settings = Application.getInstance().getActiveMachine()
  140. if self._current_settings:
  141. self._current_settings.settingChanged.connect(self._onSettingChanged)
  142. self._low_quality_settings = None
  143. self._high_quality_settings = None
  144. self._updateTimeQualitySettings()
  145. self._slice_reason = self.SliceReason.ActiveMachineChanged
  146. def _updateTimeQualitySettings(self):
  147. if not self._current_settings or not self._enabled:
  148. return
  149. if not self._low_quality_settings:
  150. self._low_quality_settings = MachineSettings()
  151. self._low_quality_settings.loadSettingsFromFile(Resources.getPath(Resources.SettingsLocation, self._current_settings.getTypeID() + ".json"))
  152. self._low_quality_settings.loadValuesFromFile(Resources.getPath(Resources.SettingsLocation, "profiles", "low_quality.conf"))
  153. if not self._high_quality_settings:
  154. self._high_quality_settings = MachineSettings()
  155. self._high_quality_settings.loadSettingsFromFile(Resources.getPath(Resources.SettingsLocation, self._current_settings.getTypeID() + ".json"))
  156. self._high_quality_settings.loadValuesFromFile(Resources.getPath(Resources.SettingsLocation, "profiles", "high_quality.conf"))
  157. for key, options in self._interpolation_settings.items():
  158. minimum_value = None
  159. if options["minimum"] == "low":
  160. minimum_value = self._low_quality_settings.getSettingValueByKey(key)
  161. elif options["minimum"] == "high":
  162. minimum_value = self._high_quality_settings.getSettingValueByKey(key)
  163. else:
  164. continue
  165. maximum_value = None
  166. if options["maximum"] == "low":
  167. maximum_value = self._low_quality_settings.getSettingValueByKey(key)
  168. elif options["maximum"] == "high":
  169. maximum_value = self._high_quality_settings.getSettingValueByKey(key)
  170. else:
  171. continue
  172. setting_value = round(minimum_value + (maximum_value - minimum_value) * (self._time_quality_value / 100), options["precision"])
  173. self._current_settings.setSettingValueByKey(key, setting_value)
  174. def _onSceneChanged(self, source):
  175. self._slice_reason = self.SliceReason.SceneChanged
  176. def _onSettingChanged(self, source):
  177. self._slice_reason = self.SliceReason.SettingChanged
  178. def _onSlicingCancelled(self):
  179. self._slice_pass = None