WorkspaceDialog.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. # Copyright (c) 2020 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. from typing import List, Optional, Dict, cast
  4. from PyQt6.QtCore import pyqtSignal, QObject, pyqtProperty, QCoreApplication
  5. from UM.FlameProfiler import pyqtSlot
  6. from UM.PluginRegistry import PluginRegistry
  7. from UM.Application import Application
  8. from UM.i18n import i18nCatalog
  9. from UM.Settings.ContainerRegistry import ContainerRegistry
  10. from cura.Settings.GlobalStack import GlobalStack
  11. from .UpdatableMachinesModel import UpdatableMachinesModel
  12. import os
  13. import threading
  14. import time
  15. from cura.CuraApplication import CuraApplication
  16. i18n_catalog = i18nCatalog("cura")
  17. class WorkspaceDialog(QObject):
  18. showDialogSignal = pyqtSignal()
  19. def __init__(self, parent = None):
  20. super().__init__(parent)
  21. self._component = None
  22. self._context = None
  23. self._view = None
  24. self._qml_url = "WorkspaceDialog.qml"
  25. self._lock = threading.Lock()
  26. self._default_strategy = None
  27. self._result = {"machine": self._default_strategy,
  28. "quality_changes": self._default_strategy,
  29. "definition_changes": self._default_strategy,
  30. "material": self._default_strategy}
  31. self._override_machine = None
  32. self._visible = False
  33. self.showDialogSignal.connect(self.__show)
  34. self._has_quality_changes_conflict = False
  35. self._has_definition_changes_conflict = False
  36. self._has_machine_conflict = False
  37. self._has_material_conflict = False
  38. self._has_visible_settings_field = False
  39. self._num_visible_settings = 0
  40. self._num_user_settings = 0
  41. self._active_mode = ""
  42. self._quality_name = ""
  43. self._num_settings_overridden_by_quality_changes = 0
  44. self._quality_type = ""
  45. self._intent_name = ""
  46. self._machine_name = ""
  47. self._machine_type = ""
  48. self._variant_type = ""
  49. self._material_labels = []
  50. self._extruders = []
  51. self._objects_on_plate = False
  52. self._is_printer_group = False
  53. self._updatable_machines_model = UpdatableMachinesModel(self)
  54. machineConflictChanged = pyqtSignal()
  55. qualityChangesConflictChanged = pyqtSignal()
  56. materialConflictChanged = pyqtSignal()
  57. numVisibleSettingsChanged = pyqtSignal()
  58. activeModeChanged = pyqtSignal()
  59. qualityNameChanged = pyqtSignal()
  60. hasVisibleSettingsFieldChanged = pyqtSignal()
  61. numSettingsOverridenByQualityChangesChanged = pyqtSignal()
  62. qualityTypeChanged = pyqtSignal()
  63. intentNameChanged = pyqtSignal()
  64. machineNameChanged = pyqtSignal()
  65. updatableMachinesChanged = pyqtSignal()
  66. materialLabelsChanged = pyqtSignal()
  67. objectsOnPlateChanged = pyqtSignal()
  68. numUserSettingsChanged = pyqtSignal()
  69. machineTypeChanged = pyqtSignal()
  70. variantTypeChanged = pyqtSignal()
  71. extrudersChanged = pyqtSignal()
  72. isPrinterGroupChanged = pyqtSignal()
  73. @pyqtProperty(bool, notify = isPrinterGroupChanged)
  74. def isPrinterGroup(self) -> bool:
  75. return self._is_printer_group
  76. def setIsPrinterGroup(self, value: bool):
  77. if value != self._is_printer_group:
  78. self._is_printer_group = value
  79. self.isPrinterGroupChanged.emit()
  80. @pyqtProperty(str, notify=variantTypeChanged)
  81. def variantType(self) -> str:
  82. return self._variant_type
  83. def setVariantType(self, variant_type: str) -> None:
  84. if self._variant_type != variant_type:
  85. self._variant_type = variant_type
  86. self.variantTypeChanged.emit()
  87. @pyqtProperty(str, notify=machineTypeChanged)
  88. def machineType(self) -> str:
  89. return self._machine_type
  90. def setMachineType(self, machine_type: str) -> None:
  91. self._machine_type = machine_type
  92. self.machineTypeChanged.emit()
  93. def setNumUserSettings(self, num_user_settings: int) -> None:
  94. if self._num_user_settings != num_user_settings:
  95. self._num_user_settings = num_user_settings
  96. self.numVisibleSettingsChanged.emit()
  97. @pyqtProperty(int, notify=numUserSettingsChanged)
  98. def numUserSettings(self) -> int:
  99. return self._num_user_settings
  100. @pyqtProperty(bool, notify=objectsOnPlateChanged)
  101. def hasObjectsOnPlate(self) -> bool:
  102. return self._objects_on_plate
  103. def setHasObjectsOnPlate(self, objects_on_plate):
  104. if self._objects_on_plate != objects_on_plate:
  105. self._objects_on_plate = objects_on_plate
  106. self.objectsOnPlateChanged.emit()
  107. @pyqtProperty("QVariantList", notify = materialLabelsChanged)
  108. def materialLabels(self) -> List[str]:
  109. return self._material_labels
  110. def setMaterialLabels(self, material_labels: List[str]) -> None:
  111. if self._material_labels != material_labels:
  112. self._material_labels = material_labels
  113. self.materialLabelsChanged.emit()
  114. @pyqtProperty("QVariantList", notify=extrudersChanged)
  115. def extruders(self):
  116. return self._extruders
  117. def setExtruders(self, extruders):
  118. if self._extruders != extruders:
  119. self._extruders = extruders
  120. self.extrudersChanged.emit()
  121. @pyqtProperty(str, notify = machineNameChanged)
  122. def machineName(self) -> str:
  123. return self._machine_name
  124. def setMachineName(self, machine_name: str) -> None:
  125. if self._machine_name != machine_name:
  126. self._machine_name = machine_name
  127. self.machineNameChanged.emit()
  128. @pyqtProperty(QObject, notify = updatableMachinesChanged)
  129. def updatableMachinesModel(self) -> UpdatableMachinesModel:
  130. return cast(UpdatableMachinesModel, self._updatable_machines_model)
  131. def setUpdatableMachines(self, updatable_machines: List[GlobalStack]) -> None:
  132. self._updatable_machines_model.update(updatable_machines)
  133. self.updatableMachinesChanged.emit()
  134. @pyqtProperty(str, notify=qualityTypeChanged)
  135. def qualityType(self) -> str:
  136. return self._quality_type
  137. def setQualityType(self, quality_type: str) -> None:
  138. if self._quality_type != quality_type:
  139. self._quality_type = quality_type
  140. self.qualityTypeChanged.emit()
  141. @pyqtProperty(int, notify=numSettingsOverridenByQualityChangesChanged)
  142. def numSettingsOverridenByQualityChanges(self) -> int:
  143. return self._num_settings_overridden_by_quality_changes
  144. def setNumSettingsOverriddenByQualityChanges(self, num_settings_overridden_by_quality_changes: int) -> None:
  145. self._num_settings_overridden_by_quality_changes = num_settings_overridden_by_quality_changes
  146. self.numSettingsOverridenByQualityChangesChanged.emit()
  147. @pyqtProperty(str, notify=qualityNameChanged)
  148. def qualityName(self) -> str:
  149. return self._quality_name
  150. def setQualityName(self, quality_name: str) -> None:
  151. if self._quality_name != quality_name:
  152. self._quality_name = quality_name
  153. self.qualityNameChanged.emit()
  154. @pyqtProperty(str, notify = intentNameChanged)
  155. def intentName(self) -> str:
  156. return self._intent_name
  157. def setIntentName(self, intent_name: str) -> None:
  158. if self._intent_name != intent_name:
  159. self._intent_name = intent_name
  160. self.intentNameChanged.emit()
  161. @pyqtProperty(str, notify=activeModeChanged)
  162. def activeMode(self) -> str:
  163. return self._active_mode
  164. def setActiveMode(self, active_mode: int) -> None:
  165. if active_mode == 0:
  166. self._active_mode = i18n_catalog.i18nc("@title:tab", "Recommended")
  167. else:
  168. self._active_mode = i18n_catalog.i18nc("@title:tab", "Custom")
  169. self.activeModeChanged.emit()
  170. @pyqtProperty(bool, notify = hasVisibleSettingsFieldChanged)
  171. def hasVisibleSettingsField(self) -> bool:
  172. return self._has_visible_settings_field
  173. def setHasVisibleSettingsField(self, has_visible_settings_field: bool) -> None:
  174. self._has_visible_settings_field = has_visible_settings_field
  175. self.hasVisibleSettingsFieldChanged.emit()
  176. @pyqtProperty(int, constant = True)
  177. def totalNumberOfSettings(self) -> int:
  178. general_definition_containers = ContainerRegistry.getInstance().findDefinitionContainers(id = "fdmprinter")
  179. if not general_definition_containers:
  180. return 0
  181. return len(general_definition_containers[0].getAllKeys())
  182. @pyqtProperty(int, notify = numVisibleSettingsChanged)
  183. def numVisibleSettings(self) -> int:
  184. return self._num_visible_settings
  185. def setNumVisibleSettings(self, num_visible_settings: int) -> None:
  186. if self._num_visible_settings != num_visible_settings:
  187. self._num_visible_settings = num_visible_settings
  188. self.numVisibleSettingsChanged.emit()
  189. @pyqtProperty(bool, notify = machineConflictChanged)
  190. def machineConflict(self) -> bool:
  191. return self._has_machine_conflict
  192. @pyqtProperty(bool, notify=qualityChangesConflictChanged)
  193. def qualityChangesConflict(self) -> bool:
  194. return self._has_quality_changes_conflict
  195. @pyqtProperty(bool, notify=materialConflictChanged)
  196. def materialConflict(self) -> bool:
  197. return self._has_material_conflict
  198. @pyqtSlot(str, str)
  199. def setResolveStrategy(self, key: str, strategy: Optional[str]) -> None:
  200. if key in self._result:
  201. self._result[key] = strategy
  202. def getMachineToOverride(self) -> str:
  203. return self._override_machine
  204. @pyqtSlot(str)
  205. def setMachineToOverride(self, machine_name: str) -> None:
  206. self._override_machine = machine_name
  207. @pyqtSlot()
  208. def closeBackend(self) -> None:
  209. """Close the backend: otherwise one could end up with "Slicing..."""
  210. Application.getInstance().getBackend().close()
  211. def setMaterialConflict(self, material_conflict: bool) -> None:
  212. if self._has_material_conflict != material_conflict:
  213. self._has_material_conflict = material_conflict
  214. self.materialConflictChanged.emit()
  215. def setMachineConflict(self, machine_conflict: bool) -> None:
  216. if self._has_machine_conflict != machine_conflict:
  217. self._has_machine_conflict = machine_conflict
  218. self.machineConflictChanged.emit()
  219. def setQualityChangesConflict(self, quality_changes_conflict: bool) -> None:
  220. if self._has_quality_changes_conflict != quality_changes_conflict:
  221. self._has_quality_changes_conflict = quality_changes_conflict
  222. self.qualityChangesConflictChanged.emit()
  223. def getResult(self) -> Dict[str, Optional[str]]:
  224. if "machine" in self._result and self.updatableMachinesModel.count <= 1:
  225. self._result["machine"] = None
  226. if "quality_changes" in self._result and not self._has_quality_changes_conflict:
  227. self._result["quality_changes"] = None
  228. if "material" in self._result and not self._has_material_conflict:
  229. self._result["material"] = None
  230. # If the machine needs to be re-created, the definition_changes should also be re-created.
  231. # If the machine strategy is None, it means that there is no name conflict with existing ones. In this case
  232. # new definitions changes are created
  233. if "machine" in self._result:
  234. if self._result["machine"] == "new" or self._result["machine"] is None and self._result["definition_changes"] is None:
  235. self._result["definition_changes"] = "new"
  236. return self._result
  237. def _createViewFromQML(self) -> None:
  238. three_mf_reader_path = PluginRegistry.getInstance().getPluginPath("3MFReader")
  239. if three_mf_reader_path:
  240. path = os.path.join(three_mf_reader_path, self._qml_url)
  241. self._view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self})
  242. def show(self) -> None:
  243. # Emit signal so the right thread actually shows the view.
  244. if threading.current_thread() != threading.main_thread():
  245. self._lock.acquire()
  246. # Reset the result
  247. self._result = {"machine": self._default_strategy,
  248. "quality_changes": self._default_strategy,
  249. "definition_changes": self._default_strategy,
  250. "material": self._default_strategy}
  251. self._visible = True
  252. self.showDialogSignal.emit()
  253. @pyqtSlot()
  254. def notifyClosed(self) -> None:
  255. """Used to notify the dialog so the lock can be released."""
  256. self._result = {} # The result should be cleared before hide, because after it is released the main thread lock
  257. self._visible = False
  258. try:
  259. self._lock.release()
  260. except:
  261. pass
  262. def hide(self) -> None:
  263. self._visible = False
  264. self._view.hide()
  265. try:
  266. self._lock.release()
  267. except:
  268. pass
  269. @pyqtSlot(bool)
  270. def _onVisibilityChanged(self, visible: bool) -> None:
  271. if not visible:
  272. try:
  273. self._lock.release()
  274. except:
  275. pass
  276. @pyqtSlot()
  277. def onOkButtonClicked(self) -> None:
  278. self._view.hide()
  279. self.hide()
  280. @pyqtSlot()
  281. def onCancelButtonClicked(self) -> None:
  282. self._result = {}
  283. self._view.hide()
  284. self.hide()
  285. def waitForClose(self) -> None:
  286. """Block thread until the dialog is closed."""
  287. if self._visible:
  288. if threading.current_thread() != threading.main_thread():
  289. self._lock.acquire()
  290. self._lock.release()
  291. else:
  292. # If this is not run from a separate thread, we need to ensure that the events are still processed.
  293. while self._visible:
  294. time.sleep(1 / 50)
  295. QCoreApplication.processEvents() # Ensure that the GUI does not freeze.
  296. def __show(self) -> None:
  297. if self._view is None:
  298. self._createViewFromQML()
  299. if self._view:
  300. self._view.show()