Browse Source

Merge remote-tracking branch 'um/3.1' into upstream3.1

tags/v3.1.1
Victor Larchenko 1 year ago
parent
commit
96294300c0
447 changed files with 8881 additions and 1613 deletions
  1. 2
    0
      .gitignore
  2. 1
    1
      CMakeLists.txt
  3. 1
    1
      cura-lulzbot.desktop.in
  4. 2
    2
      cura/CameraAnimation.py
  5. 1
    1
      cura/ConvexHullDecorator.py
  6. 243
    76
      cura/CrashHandler.py
  7. 61
    8
      cura/CuraApplication.py
  8. 1
    1
      cura/CuraSplashScreen.py
  9. 57
    0
      cura/PreviewPass.py
  10. 39
    7
      cura/PrintInformation.py
  11. 6
    0
      cura/PrinterOutputDevice.py
  12. 18
    5
      cura/QualityManager.py
  13. 3
    0
      cura/Settings/CuraContainerRegistry.py
  14. 5
    3
      cura/Settings/ExtrudersModel.py
  15. 109
    34
      cura/Settings/MachineManager.py
  16. 28
    8
      cura/Settings/ProfilesModel.py
  17. 92
    0
      cura/Settings/SimpleModeSettingsManager.py
  18. 3
    2
      cura_app.py
  19. 40
    3
      plugins/3MFReader/ThreeMFWorkspaceReader.py
  20. 18
    0
      plugins/3MFReader/WorkspaceDialog.py
  21. 2
    1
      plugins/3MFReader/WorkspaceDialog.qml
  22. 0
    1
      plugins/ChangeLogPlugin/ChangeLog.py
  23. 10
    0
      plugins/ChangeLogPlugin/ChangeLog.txt
  24. 22
    1
      plugins/CuraEngineBackend/CuraEngineBackend.py
  25. 1
    0
      plugins/CuraEngineBackend/ProcessSlicedLayersJob.py
  26. 2
    1
      plugins/CuraEngineBackend/StartSliceJob.py
  27. 17
    1
      plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py
  28. 8
    16
      plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py
  29. 323
    0
      plugins/LayerView/LayerSlider.qml
  30. 103
    0
      plugins/LayerView/LayerSliderLabel.qml
  31. 3
    2
      plugins/LayerView/LayerView.py
  32. 35
    290
      plugins/LayerView/LayerView.qml
  33. 10
    11
      plugins/LayerView/LayerViewProxy.py
  34. 14
    8
      plugins/MachineSettingsAction/MachineSettingsAction.py
  35. 2
    2
      plugins/PluginBrowser/PluginBrowser.qml
  36. 2
    4
      plugins/RemovableDriveOutputDevice/OSXRemovableDrivePlugin.py
  37. 8
    5
      plugins/RemovableDriveOutputDevice/RemovableDrivePlugin.py
  38. 1
    2
      plugins/UM3NetworkPrinting/ClusterControlItem.qml
  39. 10
    12
      plugins/UM3NetworkPrinting/DiscoverUM3Action.qml
  40. 4
    4
      plugins/UM3NetworkPrinting/MonitorItem.qml
  41. 4
    1
      plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py
  42. 36
    2
      plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py
  43. 58
    7
      plugins/UM3NetworkPrinting/NetworkPrinterOutputDevicePlugin.py
  44. 1
    1
      plugins/UM3NetworkPrinting/OpenPanelButton.qml
  45. 1
    1
      plugins/UM3NetworkPrinting/PrintCoreConfiguration.qml
  46. 6
    6
      plugins/UM3NetworkPrinting/PrinterInfoBlock.qml
  47. 1
    1
      plugins/UM3NetworkPrinting/PrinterVideoStream.qml
  48. 15
    1
      plugins/UM3NetworkPrinting/UM3InfoComponents.qml
  49. 53
    0
      plugins/UserAgreementPlugin/UserAgreement.py
  50. 64
    0
      plugins/UserAgreementPlugin/UserAgreement.qml
  51. 10
    0
      plugins/UserAgreementPlugin/__init__.py
  52. 8
    0
      plugins/UserAgreementPlugin/plugin.json
  53. 141
    0
      plugins/VersionUpgrade/VersionUpgrade30to31/VersionUpgrade30to31.py
  54. 56
    0
      plugins/VersionUpgrade/VersionUpgrade30to31/__init__.py
  55. 8
    0
      plugins/VersionUpgrade/VersionUpgrade30to31/plugin.json
  56. 3
    1
      plugins/XRayView/XRayView.py
  57. 23
    26
      plugins/XmlMaterialProfile/XmlMaterialProfile.py
  58. 5
    13
      plugins/XmlMaterialProfile/XmlMaterialUpgrader.py
  59. 1
    1
      plugins/XmlMaterialProfile/__init__.py
  60. 117
    0
      resources/definitions/builder_premium_large.def.json
  61. 117
    0
      resources/definitions/builder_premium_medium.def.json
  62. 116
    0
      resources/definitions/builder_premium_small.def.json
  63. 2
    7
      resources/definitions/creality_cr10.def.json
  64. 0
    0
      resources/definitions/dagoma_discoeasy200.def.json
  65. 66
    0
      resources/definitions/deltacomb.def.json
  66. 136
    69
      resources/definitions/fdmprinter.def.json
  67. 0
    0
      resources/definitions/helloBEEprusa.def.json
  68. 2
    2
      resources/definitions/prusa_i3_mk2.def.json
  69. 95
    0
      resources/definitions/raise3D_N2_dual.def.json
  70. 95
    0
      resources/definitions/raise3D_N2_plus_dual.def.json
  71. 87
    0
      resources/definitions/raise3D_N2_plus_single.def.json
  72. 87
    0
      resources/definitions/raise3D_N2_single.def.json
  73. 1
    1
      resources/definitions/ultimaker3.def.json
  74. 4
    1
      resources/definitions/vertex_delta_k8800.def.json
  75. 27
    0
      resources/extruders/builder_premium_large_front.def.json
  76. 27
    0
      resources/extruders/builder_premium_large_rear.def.json
  77. 27
    0
      resources/extruders/builder_premium_medium_front.def.json
  78. 27
    0
      resources/extruders/builder_premium_medium_rear.def.json
  79. 27
    0
      resources/extruders/builder_premium_small_front.def.json
  80. 27
    0
      resources/extruders/builder_premium_small_rear.def.json
  81. 0
    0
      resources/extruders/hBp_extruder_left.def.json
  82. 0
    0
      resources/extruders/hBp_extruder_right.def.json
  83. 26
    0
      resources/extruders/raise3D_N2_dual_extruder_0.def.json
  84. 28
    0
      resources/extruders/raise3D_N2_dual_extruder_1.def.json
  85. 26
    0
      resources/extruders/raise3D_N2_plus_dual_extruder_0.def.json
  86. 28
    0
      resources/extruders/raise3D_N2_plus_dual_extruder_1.def.json
  87. 15
    0
      resources/i18n/cura.pot
  88. 1
    1
      resources/i18n/de_DE/cura.po
  89. 4389
    0
      resources/i18n/ja_JP/cura.po
  90. 0
    0
      resources/meshes/BEEVERYCREATIVE-helloBEEprusa.stl
  91. BIN
      resources/meshes/builder_premium_platform.stl
  92. BIN
      resources/meshes/deltacomb.stl
  93. 3
    1
      resources/qml/AboutDialog.qml
  94. 0
    0
      resources/qml/Actions.qml
  95. 9
    1
      resources/qml/AskOpenAsProjectOrModelsDialog.qml
  96. 5
    0
      resources/qml/Cura.qml
  97. 1
    1
      resources/qml/JobSpecs.qml
  98. 0
    0
      resources/qml/Menus/ContextMenu.qml
  99. 16
    2
      resources/qml/Menus/MaterialMenu.qml
  100. 16
    2
      resources/qml/Menus/NozzleMenu.qml
  101. 2
    2
      resources/qml/Menus/ProfileMenu.qml
  102. 0
    0
      resources/qml/Preferences/GeneralPage.qml
  103. 1
    1
      resources/qml/PrintMonitor.qml
  104. 20
    5
      resources/qml/SaveButton.qml
  105. 20
    2
      resources/qml/Settings/SettingTextField.qml
  106. 19
    12
      resources/qml/Settings/SettingView.qml
  107. 125
    22
      resources/qml/Sidebar.qml
  108. 21
    51
      resources/qml/SidebarHeader.qml
  109. 98
    54
      resources/qml/SidebarSimple.qml
  110. 1
    0
      resources/qml/SidebarTooltip.qml
  111. 4
    1
      resources/qml/Toolbar.qml
  112. 4
    1
      resources/qml/Topbar.qml
  113. 1
    1
      resources/quality/abax_pri3/apri3_pla_fast.inst.cfg
  114. 1
    1
      resources/quality/abax_pri3/apri3_pla_high.inst.cfg
  115. 1
    1
      resources/quality/abax_pri3/apri3_pla_normal.inst.cfg
  116. 1
    1
      resources/quality/abax_pri5/apri5_pla_fast.inst.cfg
  117. 1
    1
      resources/quality/abax_pri5/apri5_pla_high.inst.cfg
  118. 1
    1
      resources/quality/abax_pri5/apri5_pla_normal.inst.cfg
  119. 1
    1
      resources/quality/abax_titan/atitan_pla_fast.inst.cfg
  120. 1
    1
      resources/quality/abax_titan/atitan_pla_high.inst.cfg
  121. 1
    1
      resources/quality/abax_titan/atitan_pla_normal.inst.cfg
  122. 25
    0
      resources/quality/builder_premium/bp_PLA_Coarse_Quality.inst.cfg
  123. 26
    0
      resources/quality/builder_premium/bp_PLA_High_Quality.inst.cfg
  124. 24
    0
      resources/quality/builder_premium/bp_PLA_Normal_Quality.inst.cfg
  125. 1
    1
      resources/quality/cartesio/abs/cartesio_0.25_abs_high.inst.cfg
  126. 1
    1
      resources/quality/cartesio/abs/cartesio_0.25_abs_normal.inst.cfg
  127. 1
    1
      resources/quality/cartesio/abs/cartesio_0.4_abs_high.inst.cfg
  128. 1
    1
      resources/quality/cartesio/abs/cartesio_0.4_abs_normal.inst.cfg
  129. 1
    1
      resources/quality/cartesio/abs/cartesio_0.8_abs_coarse.inst.cfg
  130. 1
    1
      resources/quality/cartesio/abs/cartesio_0.8_abs_extra_coarse.inst.cfg
  131. 1
    1
      resources/quality/cartesio/abs/cartesio_0.8_abs_high.inst.cfg
  132. 1
    1
      resources/quality/cartesio/abs/cartesio_0.8_abs_normal.inst.cfg
  133. 1
    1
      resources/quality/cartesio/arnitel/cartesio_0.4_arnitel2045_high.inst.cfg
  134. 1
    1
      resources/quality/cartesio/arnitel/cartesio_0.4_arnitel2045_normal.inst.cfg
  135. 1
    1
      resources/quality/cartesio/cartesio_global_Coarse_Quality.inst.cfg
  136. 1
    1
      resources/quality/cartesio/cartesio_global_Extra_Coarse_Quality.inst.cfg
  137. 1
    1
      resources/quality/cartesio/cartesio_global_High_Quality.inst.cfg
  138. 1
    1
      resources/quality/cartesio/cartesio_global_Normal_Quality.inst.cfg
  139. 1
    1
      resources/quality/cartesio/hips/cartesio_0.25_hips_high.inst.cfg
  140. 1
    1
      resources/quality/cartesio/hips/cartesio_0.25_hips_normal.inst.cfg
  141. 1
    1
      resources/quality/cartesio/hips/cartesio_0.4_hips_high.inst.cfg
  142. 1
    1
      resources/quality/cartesio/hips/cartesio_0.4_hips_normal.inst.cfg
  143. 1
    1
      resources/quality/cartesio/hips/cartesio_0.8_hips_coarse.inst.cfg
  144. 1
    1
      resources/quality/cartesio/hips/cartesio_0.8_hips_extra_coarse.inst.cfg
  145. 1
    1
      resources/quality/cartesio/hips/cartesio_0.8_hips_high.inst.cfg
  146. 1
    1
      resources/quality/cartesio/hips/cartesio_0.8_hips_normal.inst.cfg
  147. 1
    1
      resources/quality/cartesio/nylon/cartesio_0.25_nylon_high.inst.cfg
  148. 1
    1
      resources/quality/cartesio/nylon/cartesio_0.25_nylon_normal.inst.cfg
  149. 1
    1
      resources/quality/cartesio/nylon/cartesio_0.4_nylon_high.inst.cfg
  150. 1
    1
      resources/quality/cartesio/nylon/cartesio_0.4_nylon_normal.inst.cfg
  151. 1
    1
      resources/quality/cartesio/nylon/cartesio_0.8_nylon_coarse.inst.cfg
  152. 1
    1
      resources/quality/cartesio/nylon/cartesio_0.8_nylon_extra_coarse.inst.cfg
  153. 1
    1
      resources/quality/cartesio/nylon/cartesio_0.8_nylon_high.inst.cfg
  154. 1
    1
      resources/quality/cartesio/nylon/cartesio_0.8_nylon_normal.inst.cfg
  155. 1
    1
      resources/quality/cartesio/pc/cartesio_0.25_pc_high.inst.cfg
  156. 1
    1
      resources/quality/cartesio/pc/cartesio_0.25_pc_normal.inst.cfg
  157. 1
    1
      resources/quality/cartesio/pc/cartesio_0.4_pc_high.inst.cfg
  158. 1
    1
      resources/quality/cartesio/pc/cartesio_0.4_pc_normal.inst.cfg
  159. 1
    1
      resources/quality/cartesio/pc/cartesio_0.8_pc_coarse.inst.cfg
  160. 1
    1
      resources/quality/cartesio/pc/cartesio_0.8_pc_extra_coarse.inst.cfg
  161. 1
    1
      resources/quality/cartesio/pc/cartesio_0.8_pc_high.inst.cfg
  162. 1
    1
      resources/quality/cartesio/pc/cartesio_0.8_pc_normal.inst.cfg
  163. 1
    1
      resources/quality/cartesio/petg/cartesio_0.25_petg_high.inst.cfg
  164. 1
    1
      resources/quality/cartesio/petg/cartesio_0.25_petg_normal.inst.cfg
  165. 1
    1
      resources/quality/cartesio/petg/cartesio_0.4_petg_high.inst.cfg
  166. 1
    1
      resources/quality/cartesio/petg/cartesio_0.4_petg_normal.inst.cfg
  167. 1
    1
      resources/quality/cartesio/petg/cartesio_0.8_petg_coarse.inst.cfg
  168. 1
    1
      resources/quality/cartesio/petg/cartesio_0.8_petg_extra_coarse.inst.cfg
  169. 1
    1
      resources/quality/cartesio/petg/cartesio_0.8_petg_high.inst.cfg
  170. 1
    1
      resources/quality/cartesio/petg/cartesio_0.8_petg_normal.inst.cfg
  171. 1
    1
      resources/quality/cartesio/pla/cartesio_0.25_pla_high.inst.cfg
  172. 1
    1
      resources/quality/cartesio/pla/cartesio_0.25_pla_normal.inst.cfg
  173. 1
    1
      resources/quality/cartesio/pla/cartesio_0.4_pla_high.inst.cfg
  174. 1
    1
      resources/quality/cartesio/pla/cartesio_0.4_pla_normal.inst.cfg
  175. 1
    1
      resources/quality/cartesio/pla/cartesio_0.8_pla_coarse.inst.cfg
  176. 1
    1
      resources/quality/cartesio/pla/cartesio_0.8_pla_extra_coarse.inst.cfg
  177. 1
    1
      resources/quality/cartesio/pla/cartesio_0.8_pla_high.inst.cfg
  178. 1
    1
      resources/quality/cartesio/pla/cartesio_0.8_pla_normal.inst.cfg
  179. 1
    1
      resources/quality/cartesio/pva/cartesio_0.25_pva_high.inst.cfg
  180. 1
    1
      resources/quality/cartesio/pva/cartesio_0.25_pva_normal.inst.cfg
  181. 1
    1
      resources/quality/cartesio/pva/cartesio_0.4_pva_high.inst.cfg
  182. 1
    1
      resources/quality/cartesio/pva/cartesio_0.4_pva_normal.inst.cfg
  183. 1
    1
      resources/quality/cartesio/pva/cartesio_0.8_pva_coarse.inst.cfg
  184. 1
    1
      resources/quality/cartesio/pva/cartesio_0.8_pva_extra_coarse.inst.cfg
  185. 1
    1
      resources/quality/cartesio/pva/cartesio_0.8_pva_high.inst.cfg
  186. 1
    1
      resources/quality/cartesio/pva/cartesio_0.8_pva_normal.inst.cfg
  187. 1
    1
      resources/quality/coarse.inst.cfg
  188. 25
    0
      resources/quality/deltacomb/deltacomb_abs_fast.inst.cfg
  189. 25
    0
      resources/quality/deltacomb/deltacomb_abs_high.inst.cfg
  190. 24
    0
      resources/quality/deltacomb/deltacomb_abs_normal.inst.cfg
  191. 57
    0
      resources/quality/deltacomb/deltacomb_nylon_fast.inst.cfg
  192. 57
    0
      resources/quality/deltacomb/deltacomb_nylon_high.inst.cfg
  193. 58
    0
      resources/quality/deltacomb/deltacomb_nylon_normal.inst.cfg
  194. 24
    0
      resources/quality/deltacomb/deltacomb_pla_fast.inst.cfg
  195. 25
    0
      resources/quality/deltacomb/deltacomb_pla_high.inst.cfg
  196. 23
    0
      resources/quality/deltacomb/deltacomb_pla_normal.inst.cfg
  197. 1
    1
      resources/quality/draft.inst.cfg
  198. 1
    1
      resources/quality/extra_coarse.inst.cfg
  199. 1
    1
      resources/quality/fabtotum/fabtotum_abs_fast.inst.cfg
  200. 1
    1
      resources/quality/fabtotum/fabtotum_abs_high.inst.cfg
  201. 1
    1
      resources/quality/fabtotum/fabtotum_abs_normal.inst.cfg
  202. 1
    1
      resources/quality/fabtotum/fabtotum_nylon_fast.inst.cfg
  203. 1
    1
      resources/quality/fabtotum/fabtotum_nylon_high.inst.cfg
  204. 1
    1
      resources/quality/fabtotum/fabtotum_nylon_normal.inst.cfg
  205. 1
    1
      resources/quality/fabtotum/fabtotum_pla_fast.inst.cfg
  206. 1
    1
      resources/quality/fabtotum/fabtotum_pla_high.inst.cfg
  207. 1
    1
      resources/quality/fabtotum/fabtotum_pla_normal.inst.cfg
  208. 1
    1
      resources/quality/high.inst.cfg
  209. 1
    1
      resources/quality/imade3d_jellybox/generic_petg_0.4_coarse.inst.cfg
  210. 1
    1
      resources/quality/imade3d_jellybox/generic_petg_0.4_coarse_2-fans.inst.cfg
  211. 1
    1
      resources/quality/imade3d_jellybox/generic_petg_0.4_medium.inst.cfg
  212. 1
    1
      resources/quality/imade3d_jellybox/generic_petg_0.4_medium_2-fans.inst.cfg
  213. 1
    1
      resources/quality/imade3d_jellybox/generic_pla_0.4_coarse.inst.cfg
  214. 1
    1
      resources/quality/imade3d_jellybox/generic_pla_0.4_coarse_2-fans.inst.cfg
  215. 1
    1
      resources/quality/imade3d_jellybox/generic_pla_0.4_fine.inst.cfg
  216. 1
    1
      resources/quality/imade3d_jellybox/generic_pla_0.4_fine_2-fans.inst.cfg
  217. 1
    1
      resources/quality/imade3d_jellybox/generic_pla_0.4_medium.inst.cfg
  218. 1
    1
      resources/quality/imade3d_jellybox/generic_pla_0.4_medium_2-fans.inst.cfg
  219. 1
    1
      resources/quality/imade3d_jellybox/generic_pla_0.4_ultrafine.inst.cfg
  220. 1
    1
      resources/quality/imade3d_jellybox/generic_pla_0.4_ultrafine_2-fans.inst.cfg
  221. 1
    1
      resources/quality/kemiq_q2/kemiq_q2_beta_abs_draft.inst.cfg
  222. 1
    1
      resources/quality/kemiq_q2/kemiq_q2_beta_abs_extra_fine.inst.cfg
  223. 1
    1
      resources/quality/kemiq_q2/kemiq_q2_beta_abs_fine.inst.cfg
  224. 1
    1
      resources/quality/kemiq_q2/kemiq_q2_beta_abs_low.inst.cfg
  225. 1
    1
      resources/quality/kemiq_q2/kemiq_q2_beta_abs_normal.inst.cfg
  226. 1
    1
      resources/quality/kemiq_q2/kemiq_q2_beta_pla_draft.inst.cfg
  227. 1
    1
      resources/quality/kemiq_q2/kemiq_q2_beta_pla_extra_fine.inst.cfg
  228. 1
    1
      resources/quality/kemiq_q2/kemiq_q2_beta_pla_fine.inst.cfg
  229. 1
    1
      resources/quality/kemiq_q2/kemiq_q2_beta_pla_low.inst.cfg
  230. 1
    1
      resources/quality/kemiq_q2/kemiq_q2_beta_pla_normal.inst.cfg
  231. 1
    1
      resources/quality/kemiq_q2/kemiq_q2_gama_pla_draft.inst.cfg
  232. 1
    1
      resources/quality/kemiq_q2/kemiq_q2_gama_pla_extra_fine.inst.cfg
  233. 1
    1
      resources/quality/kemiq_q2/kemiq_q2_gama_pla_fine.inst.cfg
  234. 1
    1
      resources/quality/kemiq_q2/kemiq_q2_gama_pla_low.inst.cfg
  235. 1
    1
      resources/quality/kemiq_q2/kemiq_q2_gama_pla_normal.inst.cfg
  236. 1
    1
      resources/quality/low.inst.cfg
  237. 1
    1
      resources/quality/normal.inst.cfg
  238. 1
    1
      resources/quality/peopoly_moai/peopoly_moai_high.inst.cfg
  239. 1
    1
      resources/quality/peopoly_moai/peopoly_moai_max.inst.cfg
  240. 1
    1
      resources/quality/peopoly_moai/peopoly_moai_normal.inst.cfg
  241. 1
    1
      resources/quality/ultimaker2/um2_draft.inst.cfg
  242. 1
    1
      resources/quality/ultimaker2/um2_high.inst.cfg
  243. 1
    1
      resources/quality/ultimaker2/um2_low.inst.cfg
  244. 1
    1
      resources/quality/ultimaker2/um2_normal.inst.cfg
  245. 1
    1
      resources/quality/ultimaker2_plus/pla_0.25_normal.inst.cfg
  246. 1
    1
      resources/quality/ultimaker2_plus/pla_0.4_fast.inst.cfg
  247. 1
    1
      resources/quality/ultimaker2_plus/pla_0.4_high.inst.cfg
  248. 1
    1
      resources/quality/ultimaker2_plus/pla_0.4_normal.inst.cfg
  249. 1
    1
      resources/quality/ultimaker2_plus/pla_0.6_normal.inst.cfg
  250. 1
    1
      resources/quality/ultimaker2_plus/pla_0.8_normal.inst.cfg
  251. 1
    1
      resources/quality/ultimaker2_plus/um2p_abs_0.25_normal.inst.cfg
  252. 1
    1
      resources/quality/ultimaker2_plus/um2p_abs_0.4_fast.inst.cfg
  253. 1
    1
      resources/quality/ultimaker2_plus/um2p_abs_0.4_high.inst.cfg
  254. 1
    1
      resources/quality/ultimaker2_plus/um2p_abs_0.4_normal.inst.cfg
  255. 1
    1
      resources/quality/ultimaker2_plus/um2p_abs_0.6_normal.inst.cfg
  256. 1
    1
      resources/quality/ultimaker2_plus/um2p_abs_0.8_normal.inst.cfg
  257. 1
    1
      resources/quality/ultimaker2_plus/um2p_cpe_0.25_normal.inst.cfg
  258. 1
    1
      resources/quality/ultimaker2_plus/um2p_cpe_0.4_fast.inst.cfg
  259. 1
    1
      resources/quality/ultimaker2_plus/um2p_cpe_0.4_high.inst.cfg
  260. 1
    1
      resources/quality/ultimaker2_plus/um2p_cpe_0.4_normal.inst.cfg
  261. 1
    1
      resources/quality/ultimaker2_plus/um2p_cpe_0.6_normal.inst.cfg
  262. 1
    1
      resources/quality/ultimaker2_plus/um2p_cpe_0.8_normal.inst.cfg
  263. 1
    1
      resources/quality/ultimaker2_plus/um2p_cpep_0.4_draft.inst.cfg
  264. 1
    1
      resources/quality/ultimaker2_plus/um2p_cpep_0.4_normal.inst.cfg
  265. 1
    1
      resources/quality/ultimaker2_plus/um2p_cpep_0.6_draft.inst.cfg
  266. 1
    1
      resources/quality/ultimaker2_plus/um2p_cpep_0.6_normal.inst.cfg
  267. 1
    1
      resources/quality/ultimaker2_plus/um2p_cpep_0.8_draft.inst.cfg
  268. 1
    1
      resources/quality/ultimaker2_plus/um2p_cpep_0.8_normal.inst.cfg
  269. 1
    1
      resources/quality/ultimaker2_plus/um2p_nylon_0.25_high.inst.cfg
  270. 1
    1
      resources/quality/ultimaker2_plus/um2p_nylon_0.25_normal.inst.cfg
  271. 1
    1
      resources/quality/ultimaker2_plus/um2p_nylon_0.4_fast.inst.cfg
  272. 1
    1
      resources/quality/ultimaker2_plus/um2p_nylon_0.4_normal.inst.cfg
  273. 1
    1
      resources/quality/ultimaker2_plus/um2p_nylon_0.6_fast.inst.cfg
  274. 1
    1
      resources/quality/ultimaker2_plus/um2p_nylon_0.6_normal.inst.cfg
  275. 1
    1
      resources/quality/ultimaker2_plus/um2p_nylon_0.8_draft.inst.cfg
  276. 1
    1
      resources/quality/ultimaker2_plus/um2p_nylon_0.8_normal.inst.cfg
  277. 1
    1
      resources/quality/ultimaker2_plus/um2p_pc_0.25_high.inst.cfg
  278. 1
    1
      resources/quality/ultimaker2_plus/um2p_pc_0.25_normal.inst.cfg
  279. 1
    1
      resources/quality/ultimaker2_plus/um2p_pc_0.4_fast.inst.cfg
  280. 1
    1
      resources/quality/ultimaker2_plus/um2p_pc_0.4_normal.inst.cfg
  281. 1
    1
      resources/quality/ultimaker2_plus/um2p_pc_0.6_fast.inst.cfg
  282. 1
    1
      resources/quality/ultimaker2_plus/um2p_pc_0.6_normal.inst.cfg
  283. 1
    1
      resources/quality/ultimaker2_plus/um2p_pc_0.8_draft.inst.cfg
  284. 1
    1
      resources/quality/ultimaker2_plus/um2p_pc_0.8_normal.inst.cfg
  285. 0
    15
      resources/quality/ultimaker2_plus/um2p_pp_0.25_normal.inst.cfg
  286. 1
    1
      resources/quality/ultimaker2_plus/um2p_pp_0.4_fast.inst.cfg
  287. 1
    1
      resources/quality/ultimaker2_plus/um2p_pp_0.4_normal.inst.cfg
  288. 1
    1
      resources/quality/ultimaker2_plus/um2p_pp_0.6_draft.inst.cfg
  289. 1
    1
      resources/quality/ultimaker2_plus/um2p_pp_0.6_fast.inst.cfg
  290. 1
    1
      resources/quality/ultimaker2_plus/um2p_pp_0.8_draft.inst.cfg
  291. 1
    1
      resources/quality/ultimaker2_plus/um2p_pp_0.8_verydraft.inst.cfg
  292. 1
    1
      resources/quality/ultimaker2_plus/um2p_tpu_0.25_high.inst.cfg
  293. 1
    1
      resources/quality/ultimaker2_plus/um2p_tpu_0.4_normal.inst.cfg
  294. 1
    1
      resources/quality/ultimaker2_plus/um2p_tpu_0.6_fast.inst.cfg
  295. 0
    15
      resources/quality/ultimaker2_plus/um2p_tpu_0.8_normal.inst.cfg
  296. 24
    0
      resources/quality/ultimaker3/um3_aa0.25_ABS_Normal_Quality.inst.cfg
  297. 22
    0
      resources/quality/ultimaker3/um3_aa0.25_CPE_Normal_Quality.inst.cfg
  298. 37
    0
      resources/quality/ultimaker3/um3_aa0.25_Nylon_Normal_Quality.inst.cfg
  299. 53
    0
      resources/quality/ultimaker3/um3_aa0.25_PC_Normal_Quality.inst.cfg
  300. 37
    0
      resources/quality/ultimaker3/um3_aa0.25_PLA_Normal_Quality.inst.cfg
  301. 60
    0
      resources/quality/ultimaker3/um3_aa0.25_PP_Normal_Quality.inst.cfg
  302. 1
    1
      resources/quality/ultimaker3/um3_aa0.4_ABS_Draft_Print.inst.cfg
  303. 1
    1
      resources/quality/ultimaker3/um3_aa0.4_ABS_Fast_Print.inst.cfg
  304. 1
    1
      resources/quality/ultimaker3/um3_aa0.4_ABS_High_Quality.inst.cfg
  305. 1
    1
      resources/quality/ultimaker3/um3_aa0.4_ABS_Normal_Quality.inst.cfg
  306. 38
    0
      resources/quality/ultimaker3/um3_aa0.4_BAM_Draft_Print.inst.cfg
  307. 38
    0
      resources/quality/ultimaker3/um3_aa0.4_BAM_Fast_Print.inst.cfg
  308. 36
    0
      resources/quality/ultimaker3/um3_aa0.4_BAM_Normal_Quality.inst.cfg
  309. 1
    1
      resources/quality/ultimaker3/um3_aa0.4_CPEP_Draft_Print.inst.cfg
  310. 1
    1
      resources/quality/ultimaker3/um3_aa0.4_CPEP_Fast_Print.inst.cfg
  311. 1
    1
      resources/quality/ultimaker3/um3_aa0.4_CPEP_High_Quality.inst.cfg
  312. 1
    1
      resources/quality/ultimaker3/um3_aa0.4_CPEP_Normal_Quality.inst.cfg
  313. 1
    1
      resources/quality/ultimaker3/um3_aa0.4_CPE_Draft_Print.inst.cfg
  314. 1
    1
      resources/quality/ultimaker3/um3_aa0.4_CPE_Fast_Print.inst.cfg
  315. 1
    1
      resources/quality/ultimaker3/um3_aa0.4_CPE_High_Quality.inst.cfg
  316. 1
    1
      resources/quality/ultimaker3/um3_aa0.4_CPE_Normal_Quality.inst.cfg
  317. 1
    1
      resources/quality/ultimaker3/um3_aa0.4_Nylon_Draft_Print.inst.cfg
  318. 1
    1
      resources/quality/ultimaker3/um3_aa0.4_Nylon_Fast_Print.inst.cfg
  319. 1
    1
      resources/quality/ultimaker3/um3_aa0.4_Nylon_High_Quality.inst.cfg
  320. 1
    1
      resources/quality/ultimaker3/um3_aa0.4_Nylon_Normal_Quality.inst.cfg
  321. 1
    1
      resources/quality/ultimaker3/um3_aa0.4_PC_Draft_Print.inst.cfg
  322. 1
    1
      resources/quality/ultimaker3/um3_aa0.4_PC_Fast_Print.inst.cfg
  323. 1
    1
      resources/quality/ultimaker3/um3_aa0.4_PC_High_Quality.inst.cfg
  324. 1
    1
      resources/quality/ultimaker3/um3_aa0.4_PC_Normal_Quality.inst.cfg
  325. 1
    1
      resources/quality/ultimaker3/um3_aa0.4_PLA_Draft_Print.inst.cfg
  326. 1
    1
      resources/quality/ultimaker3/um3_aa0.4_PLA_Fast_Print.inst.cfg
  327. 1
    1
      resources/quality/ultimaker3/um3_aa0.4_PLA_High_Quality.inst.cfg
  328. 1
    1
      resources/quality/ultimaker3/um3_aa0.4_PLA_Normal_Quality.inst.cfg
  329. 1
    1
      resources/quality/ultimaker3/um3_aa0.4_PP_Draft_Print.inst.cfg
  330. 1
    1
      resources/quality/ultimaker3/um3_aa0.4_PP_Fast_Print.inst.cfg
  331. 1
    1
      resources/quality/ultimaker3/um3_aa0.4_PP_Normal_Quality.inst.cfg
  332. 0
    14
      resources/quality/ultimaker3/um3_aa0.4_PVA_Fast_Print.inst.cfg
  333. 1
    1
      resources/quality/ultimaker3/um3_aa0.4_TPU_Draft_Print.inst.cfg
  334. 1
    1
      resources/quality/ultimaker3/um3_aa0.4_TPU_Fast_Print.inst.cfg
  335. 1
    1
      resources/quality/ultimaker3/um3_aa0.4_TPU_Normal_Quality.inst.cfg
  336. 1
    1
      resources/quality/ultimaker3/um3_aa0.8_ABS_Draft_Print.inst.cfg
  337. 1
    1
      resources/quality/ultimaker3/um3_aa0.8_ABS_Superdraft_Print.inst.cfg
  338. 1
    1
      resources/quality/ultimaker3/um3_aa0.8_ABS_Verydraft_Print.inst.cfg
  339. 1
    1
      resources/quality/ultimaker3/um3_aa0.8_CPEP_Fast_Print.inst.cfg
  340. 1
    1
      resources/quality/ultimaker3/um3_aa0.8_CPEP_Superdraft_Print.inst.cfg
  341. 1
    1
      resources/quality/ultimaker3/um3_aa0.8_CPEP_Verydraft_Print.inst.cfg
  342. 1
    1
      resources/quality/ultimaker3/um3_aa0.8_CPE_Draft_Print.inst.cfg
  343. 1
    1
      resources/quality/ultimaker3/um3_aa0.8_CPE_Superdraft_Print.inst.cfg
  344. 1
    1
      resources/quality/ultimaker3/um3_aa0.8_CPE_Verydraft_Print.inst.cfg
  345. 1
    1
      resources/quality/ultimaker3/um3_aa0.8_Nylon_Draft_Print.inst.cfg
  346. 1
    1
      resources/quality/ultimaker3/um3_aa0.8_Nylon_Superdraft_Print.inst.cfg
  347. 1
    1
      resources/quality/ultimaker3/um3_aa0.8_Nylon_Verydraft_Print.inst.cfg
  348. 1
    1
      resources/quality/ultimaker3/um3_aa0.8_PC_Fast_Print.inst.cfg
  349. 1
    1
      resources/quality/ultimaker3/um3_aa0.8_PC_Superdraft_Print.inst.cfg
  350. 1
    1
      resources/quality/ultimaker3/um3_aa0.8_PC_Verydraft_Print.inst.cfg
  351. 1
    1
      resources/quality/ultimaker3/um3_aa0.8_PLA_Draft_Print.inst.cfg
  352. 1
    1
      resources/quality/ultimaker3/um3_aa0.8_PLA_Superdraft_Print.inst.cfg
  353. 1
    1
      resources/quality/ultimaker3/um3_aa0.8_PLA_Verydraft_Print.inst.cfg
  354. 1
    1
      resources/quality/ultimaker3/um3_aa0.8_PP_Draft_Print.inst.cfg
  355. 1
    1
      resources/quality/ultimaker3/um3_aa0.8_PP_Superdraft_Print.inst.cfg
  356. 1
    1
      resources/quality/ultimaker3/um3_aa0.8_PP_Verydraft_Print.inst.cfg
  357. 0
    14
      resources/quality/ultimaker3/um3_aa0.8_PVA_Fast_Print.inst.cfg
  358. 0
    14
      resources/quality/ultimaker3/um3_aa0.8_PVA_Superdraft_Print.inst.cfg
  359. 1
    1
      resources/quality/ultimaker3/um3_aa0.8_TPU_Draft_Print.inst.cfg
  360. 1
    1
      resources/quality/ultimaker3/um3_aa0.8_TPU_Superdraft_Print.inst.cfg
  361. 1
    1
      resources/quality/ultimaker3/um3_aa0.8_TPU_Verydraft_Print.inst.cfg
  362. 0
    14
      resources/quality/ultimaker3/um3_bb0.4_ABS_Fast_Print.inst.cfg
  363. 0
    14
      resources/quality/ultimaker3/um3_bb0.4_ABS_Superdraft_Print.inst.cfg
  364. 0
    14
      resources/quality/ultimaker3/um3_bb0.4_CPEP_Fast_Print.inst.cfg
  365. 0
    14
      resources/quality/ultimaker3/um3_bb0.4_CPEP_Superdraft_Print.inst.cfg
  366. 0
    14
      resources/quality/ultimaker3/um3_bb0.4_CPE_Fast_Print.inst.cfg
  367. 0
    14
      resources/quality/ultimaker3/um3_bb0.4_CPE_Superdraft_Print.inst.cfg
  368. 0
    14
      resources/quality/ultimaker3/um3_bb0.4_Nylon_Fast_Print.inst.cfg
  369. 0
    14
      resources/quality/ultimaker3/um3_bb0.4_Nylon_Superdraft_Print.inst.cfg
  370. 0
    14
      resources/quality/ultimaker3/um3_bb0.4_PC_Fast_Print.inst.cfg
  371. 0
    14
      resources/quality/ultimaker3/um3_bb0.4_PLA_Fast_Print.inst.cfg
  372. 0
    14
      resources/quality/ultimaker3/um3_bb0.4_PLA_Superdraft_Print.inst.cfg
  373. 0
    14
      resources/quality/ultimaker3/um3_bb0.4_PP_Fast_Print.inst.cfg
  374. 0
    14
      resources/quality/ultimaker3/um3_bb0.4_PP_Superdraft_Print.inst.cfg
  375. 1
    1
      resources/quality/ultimaker3/um3_bb0.4_PVA_Draft_Print.inst.cfg
  376. 1
    1
      resources/quality/ultimaker3/um3_bb0.4_PVA_Fast_Print.inst.cfg
  377. 1
    1
      resources/quality/ultimaker3/um3_bb0.4_PVA_High_Quality.inst.cfg
  378. 1
    1
      resources/quality/ultimaker3/um3_bb0.4_PVA_Normal_Quality.inst.cfg
  379. 0
    14
      resources/quality/ultimaker3/um3_bb0.4_TPU_Fast_Print.inst.cfg
  380. 0
    14
      resources/quality/ultimaker3/um3_bb0.4_TPU_Superdraft_Print.inst.cfg
  381. 0
    14
      resources/quality/ultimaker3/um3_bb0.8_ABS_Fast_Print.inst.cfg
  382. 0
    14
      resources/quality/ultimaker3/um3_bb0.8_ABS_Superdraft_Print.inst.cfg
  383. 0
    14
      resources/quality/ultimaker3/um3_bb0.8_CPEP_Fast_Print.inst.cfg
  384. 0
    14
      resources/quality/ultimaker3/um3_bb0.8_CPEP_Superdraft_Print.inst.cfg
  385. 0
    14
      resources/quality/ultimaker3/um3_bb0.8_CPE_Fast_Print.inst.cfg
  386. 0
    14
      resources/quality/ultimaker3/um3_bb0.8_CPE_Superdraft_Print.inst.cfg
  387. 0
    14
      resources/quality/ultimaker3/um3_bb0.8_Nylon_Fast_Print.inst.cfg
  388. 0
    14
      resources/quality/ultimaker3/um3_bb0.8_Nylon_Superdraft_Print.inst.cfg
  389. 0
    14
      resources/quality/ultimaker3/um3_bb0.8_PC_Fast_Print.inst.cfg
  390. 0
    14
      resources/quality/ultimaker3/um3_bb0.8_PC_Superdraft_Print.inst.cfg
  391. 0
    14
      resources/quality/ultimaker3/um3_bb0.8_PLA_Fast_Print.inst.cfg
  392. 0
    14
      resources/quality/ultimaker3/um3_bb0.8_PLA_Superdraft_Print.inst.cfg
  393. 0
    14
      resources/quality/ultimaker3/um3_bb0.8_PP_Fast_Print.inst.cfg
  394. 0
    14
      resources/quality/ultimaker3/um3_bb0.8_PP_Superdraft_Print.inst.cfg
  395. 1
    1
      resources/quality/ultimaker3/um3_bb0.8_PVA_Draft_Print.inst.cfg
  396. 1
    1
      resources/quality/ultimaker3/um3_bb0.8_PVA_Superdraft_Print.inst.cfg
  397. 1
    1
      resources/quality/ultimaker3/um3_bb0.8_PVA_Verydraft_Print.inst.cfg
  398. 0
    14
      resources/quality/ultimaker3/um3_bb0.8_TPU_Fast_print.inst.cfg
  399. 0
    14
      resources/quality/ultimaker3/um3_bb0.8_TPU_Superdraft_Print.inst.cfg
  400. 1
    1
      resources/quality/ultimaker3/um3_global_Draft_Quality.inst.cfg
  401. 1
    1
      resources/quality/ultimaker3/um3_global_Fast_Quality.inst.cfg
  402. 1
    1
      resources/quality/ultimaker3/um3_global_High_Quality.inst.cfg
  403. 1
    1
      resources/quality/ultimaker3/um3_global_Normal_Quality.inst.cfg
  404. 1
    1
      resources/quality/ultimaker3/um3_global_Superdraft_Quality.inst.cfg
  405. 1
    1
      resources/quality/ultimaker3/um3_global_Verydraft_Quality.inst.cfg
  406. 3
    0
      resources/themes/cura-light/icons/home.svg
  407. 13
    13
      resources/themes/cura-light/theme.json
  408. 1
    1
      resources/variants/cartesio_0.25.inst.cfg
  409. 1
    1
      resources/variants/cartesio_0.4.inst.cfg
  410. 1
    1
      resources/variants/cartesio_0.8.inst.cfg
  411. 1
    1
      resources/variants/fabtotum_hyb35.inst.cfg
  412. 1
    1
      resources/variants/fabtotum_lite04.inst.cfg
  413. 1
    1
      resources/variants/fabtotum_lite06.inst.cfg
  414. 1
    1
      resources/variants/fabtotum_pro02.inst.cfg
  415. 1
    1
      resources/variants/fabtotum_pro04.inst.cfg
  416. 1
    1
      resources/variants/fabtotum_pro06.inst.cfg
  417. 1
    1
      resources/variants/fabtotum_pro08.inst.cfg
  418. 1
    1
      resources/variants/imade3d_jellybox_0.4.inst.cfg
  419. 1
    1
      resources/variants/imade3d_jellybox_0.4_2-fans.inst.cfg
  420. 1
    1
      resources/variants/ultimaker2_0.25.inst.cfg
  421. 1
    1
      resources/variants/ultimaker2_0.4.inst.cfg
  422. 1
    1
      resources/variants/ultimaker2_0.6.inst.cfg
  423. 1
    1
      resources/variants/ultimaker2_0.8.inst.cfg
  424. 1
    1
      resources/variants/ultimaker2_extended_0.25.inst.cfg
  425. 1
    1
      resources/variants/ultimaker2_extended_0.4.inst.cfg
  426. 1
    1
      resources/variants/ultimaker2_extended_0.6.inst.cfg
  427. 1
    1
      resources/variants/ultimaker2_extended_0.8.inst.cfg
  428. 1
    1
      resources/variants/ultimaker2_extended_plus_0.25.inst.cfg
  429. 1
    1
      resources/variants/ultimaker2_extended_plus_0.4.inst.cfg
  430. 1
    1
      resources/variants/ultimaker2_extended_plus_0.6.inst.cfg
  431. 1
    1
      resources/variants/ultimaker2_extended_plus_0.8.inst.cfg
  432. 1
    1
      resources/variants/ultimaker2_plus_0.25.inst.cfg
  433. 1
    1
      resources/variants/ultimaker2_plus_0.4.inst.cfg
  434. 1
    1
      resources/variants/ultimaker2_plus_0.6.inst.cfg
  435. 1
    1
      resources/variants/ultimaker2_plus_0.8.inst.cfg
  436. 40
    0
      resources/variants/ultimaker3_aa0.25.inst.cfg
  437. 1
    1
      resources/variants/ultimaker3_aa0.8.inst.cfg
  438. 1
    1
      resources/variants/ultimaker3_aa04.inst.cfg
  439. 1
    1
      resources/variants/ultimaker3_bb0.8.inst.cfg
  440. 1
    1
      resources/variants/ultimaker3_bb04.inst.cfg
  441. 40
    0
      resources/variants/ultimaker3_extended_aa0.25.inst.cfg
  442. 1
    1
      resources/variants/ultimaker3_extended_aa0.8.inst.cfg
  443. 1
    1
      resources/variants/ultimaker3_extended_aa04.inst.cfg
  444. 1
    1
      resources/variants/ultimaker3_extended_bb0.8.inst.cfg
  445. 1
    1
      resources/variants/ultimaker3_extended_bb04.inst.cfg
  446. 1
    1
      tests/Settings/TestExtruderStack.py
  447. 8
    8
      tests/Settings/TestGlobalStack.py

+ 2
- 0
.gitignore View File

@@ -42,6 +42,7 @@ plugins/FlatProfileExporter
plugins/ProfileFlattener
plugins/cura-god-mode-plugin
plugins/cura-big-flame-graph
plugins/cura-siemensnx-plugin

#Build stuff
CMakeCache.txt
@@ -58,4 +59,5 @@ cmake_install.cmake
run.sh
CuraEngine
debug.html
.scannerwork/


+ 1
- 1
CMakeLists.txt View File

@@ -88,7 +88,7 @@ if(NOT APPLE AND NOT WIN32)
install(FILES ${CMAKE_SOURCE_DIR}/resources/images/cura-icon.png
DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps/)
install(FILES cura.appdata.xml
DESTINATION ${CMAKE_INSTALL_DATADIR}/appdata)
DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo)
install(FILES cura.sharedmimeinfo
DESTINATION ${CMAKE_INSTALL_DATADIR}/mime/packages/
RENAME cura.xml )

+ 1
- 1
cura-lulzbot.desktop.in View File

@@ -10,6 +10,6 @@ Comment[de]=Cura wandelt 3D-Modelle in Pfade für einen 3D-Drucker um. Es bereit
Icon=@CMAKE_INSTALL_FULL_DATADIR@/cura/resources/images/cura-icon.png
Terminal=false
Type=Application
MimeType=application/sla;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;image/bmp;image/gif;image/jpeg;image/png;
MimeType=application/sla;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;image/bmp;image/gif;image/jpeg;image/png;model/x3d+xml;
Categories=Graphics;
Keywords=3D;Printing;

+ 2
- 2
cura/CameraAnimation.py View File

@@ -12,8 +12,8 @@ class CameraAnimation(QVariantAnimation):
def __init__(self, parent = None):
super().__init__(parent)
self._camera_tool = None
self.setDuration(500)
self.setEasingCurve(QEasingCurve.InOutQuad)
self.setDuration(300)
self.setEasingCurve(QEasingCurve.OutQuad)

def setCameraTool(self, camera_tool):
self._camera_tool = camera_tool

+ 1
- 1
cura/ConvexHullDecorator.py View File

@@ -269,7 +269,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
if self._getSettingProperty("mold_enabled", "value"):
mold_width = self._getSettingProperty("mold_width", "value")
hull_offset = horizontal_expansion + mold_width
if hull_offset != 0:
if hull_offset > 0: #TODO: Implement Minkowski subtraction for if the offset < 0.
expansion_polygon = Polygon(numpy.array([
[-hull_offset, -hull_offset],
[-hull_offset, hull_offset],

+ 243
- 76
cura/CrashHandler.py View File

@@ -1,18 +1,28 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.

import sys
import platform
import traceback
import webbrowser
import faulthandler
import tempfile
import os
import urllib
import os.path
import time
import json
import ssl
import urllib.request
import urllib.error

from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, Qt, QCoreApplication
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QHBoxLayout, QVBoxLayout, QLabel, QTextEdit
from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, QCoreApplication
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QTextEdit, QGroupBox

from UM.Application import Application
from UM.Logger import Logger
from UM.View.GL.OpenGL import OpenGL
from UM.i18n import i18nCatalog
from UM.Platform import Platform

catalog = i18nCatalog("cura")

MYPY = False
@@ -35,82 +45,239 @@ fatal_exception_types = [
SystemError,
]

def show(exception_type, value, tb):
Logger.log("c", "An uncaught exception has occurred!")
for line in traceback.format_exception(exception_type, value, tb):
for part in line.rstrip("\n").split("\n"):
Logger.log("c", part)

if not CuraDebugMode and exception_type not in fatal_exception_types:
return

application = QCoreApplication.instance()
if not application:
sys.exit(1)

dialog = QDialog()
dialog.setMinimumWidth(640)
dialog.setMinimumHeight(640)
dialog.setWindowTitle(catalog.i18nc("@title:window", "Crash Report"))

layout = QVBoxLayout(dialog)

#label = QLabel(dialog)
#pixmap = QPixmap()
#try:
# data = urllib.request.urlopen("http://www.randomkittengenerator.com/cats/rotator.php").read()
# pixmap.loadFromData(data)
#except:
# try:
# from UM.Resources import Resources
# path = Resources.getPath(Resources.Images, "kitten.jpg")
# pixmap.load(path)
# except:
# pass
#pixmap = pixmap.scaled(150, 150)
#label.setPixmap(pixmap)
#label.setAlignment(Qt.AlignCenter)
#layout.addWidget(label)

label = QLabel(dialog)
layout.addWidget(label)
#label.setScaledContents(True)
label.setText(catalog.i18nc("@label", """<p>A fatal exception has occurred that we could not recover from!</p>
<p>Please use the information below to post a bug report at <a href=\"http://github.com/Ultimaker/Cura/issues\">http://github.com/Ultimaker/Cura/issues</a></p>
"""))

textarea = QTextEdit(dialog)
layout.addWidget(textarea)
class CrashHandler:
crash_url = "https://stats.ultimaker.com/api/cura"

try:
from UM.Application import Application
version = Application.getInstance().getVersion()
except:
version = "Unknown"
def __init__(self, exception_type, value, tb):
self.exception_type = exception_type
self.value = value
self.traceback = tb
self.dialog = QDialog()

# While we create the GUI, the information will be stored for sending afterwards
self.data = dict()
self.data["time_stamp"] = time.time()

Logger.log("c", "An uncaught exception has occurred!")
for line in traceback.format_exception(exception_type, value, tb):
for part in line.rstrip("\n").split("\n"):
Logger.log("c", part)

if not CuraDebugMode and exception_type not in fatal_exception_types:
return

application = QCoreApplication.instance()
if not application:
sys.exit(1)

self._createDialog()

## Creates a modal dialog.
def _createDialog(self):
self.dialog.setMinimumWidth(640)
self.dialog.setMinimumHeight(640)
self.dialog.setWindowTitle(catalog.i18nc("@title:window", "Crash Report"))

layout = QVBoxLayout(self.dialog)

layout.addWidget(self._messageWidget())
layout.addWidget(self._informationWidget())
layout.addWidget(self._exceptionInfoWidget())
layout.addWidget(self._logInfoWidget())
layout.addWidget(self._userDescriptionWidget())
layout.addWidget(self._buttonsWidget())

def _messageWidget(self):
label = QLabel()
label.setText(catalog.i18nc("@label crash message", """<p><b>A fatal exception has occurred. Please send us this Crash Report to fix the problem</p></b>
<p>Please use the "Send report" button to post a bug report automatically to our servers</p>
"""))

return label

def _informationWidget(self):
group = QGroupBox()
group.setTitle(catalog.i18nc("@title:groupbox", "System information"))
layout = QVBoxLayout()
label = QLabel()

try:
from UM.Application import Application
self.cura_version = Application.getInstance().getVersion()
except:
self.cura_version = catalog.i18nc("@label unknown version of Cura", "Unknown")

crash_info = catalog.i18nc("@label Cura version", "<b>Cura version:</b> {version}<br/>").format(version = self.cura_version)
crash_info += catalog.i18nc("@label Platform", "<b>Platform:</b> {platform}<br/>").format(platform = platform.platform())
crash_info += catalog.i18nc("@label Qt version", "<b>Qt version:</b> {qt}<br/>").format(qt = QT_VERSION_STR)
crash_info += catalog.i18nc("@label PyQt version", "<b>PyQt version:</b> {pyqt}<br/>").format(pyqt = PYQT_VERSION_STR)
crash_info += catalog.i18nc("@label OpenGL", "<b>OpenGL:</b> {opengl}<br/>").format(opengl = self._getOpenGLInfo())
label.setText(crash_info)

layout.addWidget(label)
group.setLayout(layout)

self.data["cura_version"] = self.cura_version
self.data["os"] = {"type": platform.system(), "version": platform.version()}
self.data["qt_version"] = QT_VERSION_STR
self.data["pyqt_version"] = PYQT_VERSION_STR

return group

def _getOpenGLInfo(self):
info = "<ul>"
info += catalog.i18nc("@label OpenGL version", "<li>OpenGL Version: {version}</li>").format(version = OpenGL.getInstance().getOpenGLVersion())
info += catalog.i18nc("@label OpenGL vendor", "<li>OpenGL Vendor: {vendor}</li>").format(vendor = OpenGL.getInstance().getGPUVendorName())
info += catalog.i18nc("@label OpenGL renderer", "<li>OpenGL Renderer: {renderer}</li>").format(renderer = OpenGL.getInstance().getGPUType())
info += "</ul>"

self.data["opengl"] = {"version": OpenGL.getInstance().getOpenGLVersion(), "vendor": OpenGL.getInstance().getGPUVendorName(), "type": OpenGL.getInstance().getGPUType()}

return info

def _exceptionInfoWidget(self):
group = QGroupBox()
group.setTitle(catalog.i18nc("@title:groupbox", "Exception traceback"))
layout = QVBoxLayout()

text_area = QTextEdit()
trace_dict = traceback.format_exception(self.exception_type, self.value, self.traceback)
trace = "".join(trace_dict)
text_area.setText(trace)
text_area.setReadOnly(True)

layout.addWidget(text_area)
group.setLayout(layout)

# Parsing all the information to fill the dictionary
summary = trace_dict[len(trace_dict)-1].rstrip("\n")
module = trace_dict[len(trace_dict)-2].rstrip("\n").split("\n")
module_split = module[0].split(", ")
filepath = module_split[0].split("\"")[1]
directory, filename = os.path.split(filepath)
line = int(module_split[1].lstrip("line "))
function = module_split[2].lstrip("in ")
code = module[1].lstrip(" ")

# Using this workaround for a cross-platform path splitting
split_path = []
folder_name = ""
# Split until reach folder "cura"
while folder_name != "cura":
directory, folder_name = os.path.split(directory)
if not folder_name:
break
split_path.append(folder_name)

# Look for plugins. If it's not a plugin, the current cura version is set
isPlugin = False
module_version = self.cura_version
module_name = "Cura"
if split_path.__contains__("plugins"):
isPlugin = True
# Look backwards until plugin.json is found
directory, name = os.path.split(filepath)
while not os.listdir(directory).__contains__("plugin.json"):
directory, name = os.path.split(directory)

json_metadata_file = os.path.join(directory, "plugin.json")
try:
with open(json_metadata_file, "r") as f:
try:
metadata = json.loads(f.read())
module_version = metadata["version"]
module_name = metadata["name"]
except json.decoder.JSONDecodeError:
# Not throw new exceptions
Logger.logException("e", "Failed to parse plugin.json for plugin %s", name)
except:
# Not throw new exceptions
pass

exception_dict = dict()
exception_dict["traceback"] = {"summary": summary, "full_trace": trace}
exception_dict["location"] = {"path": filepath, "file": filename, "function": function, "code": code, "line": line,
"module_name": module_name, "version": module_version, "is_plugin": isPlugin}
self.data["exception"] = exception_dict

return group

def _logInfoWidget(self):
group = QGroupBox()
group.setTitle(catalog.i18nc("@title:groupbox", "Logs"))
layout = QVBoxLayout()

text_area = QTextEdit()
tmp_file_fd, tmp_file_path = tempfile.mkstemp(prefix = "cura-crash", text = True)
os.close(tmp_file_fd)
with open(tmp_file_path, "w") as f:
faulthandler.dump_traceback(f, all_threads=True)
with open(tmp_file_path, "r") as f:
logdata = f.read()

text_area.setText(logdata)
text_area.setReadOnly(True)

layout.addWidget(text_area)
group.setLayout(layout)

self.data["log"] = logdata

return group

def _userDescriptionWidget(self):
group = QGroupBox()
group.setTitle(catalog.i18nc("@title:groupbox", "User description"))
layout = QVBoxLayout()

# When sending the report, the user comments will be collected
self.user_description_text_area = QTextEdit()
self.user_description_text_area.setFocus(True)

layout.addWidget(self.user_description_text_area)
group.setLayout(layout)

return group

def _buttonsWidget(self):
buttons = QDialogButtonBox()
buttons.addButton(QDialogButtonBox.Close)
buttons.addButton(catalog.i18nc("@action:button", "Send report"), QDialogButtonBox.AcceptRole)
buttons.rejected.connect(self.dialog.close)
buttons.accepted.connect(self._sendCrashReport)

return buttons

def _sendCrashReport(self):
# Before sending data, the user comments are stored
self.data["user_info"] = self.user_description_text_area.toPlainText()

# Convert data to bytes
binary_data = json.dumps(self.data).encode("utf-8")

trace = "".join(traceback.format_exception(exception_type, value, tb))
# Submit data
kwoptions = {"data": binary_data, "timeout": 5}

crash_info = "Version: {0}\nPlatform: {1}\nQt: {2}\nPyQt: {3}\n\nException:\n{4}"
crash_info = crash_info.format(version, platform.platform(), QT_VERSION_STR, PYQT_VERSION_STR, trace)
if Platform.isOSX():
kwoptions["context"] = ssl._create_unverified_context()

tmp_file_fd, tmp_file_path = tempfile.mkstemp(prefix = "cura-crash", text = True)
os.close(tmp_file_fd)
with open(tmp_file_path, "w") as f:
faulthandler.dump_traceback(f, all_threads=True)
with open(tmp_file_path, "r") as f:
data = f.read()
Logger.log("i", "Sending crash report info to [%s]...", self.crash_url)

msg = "-------------------------\n"
msg += data
crash_info += "\n\n" + msg
try:
f = urllib.request.urlopen(self.crash_url, **kwoptions)
Logger.log("i", "Sent crash report info.")
f.close()
except urllib.error.HTTPError:
Logger.logException("e", "An HTTP error occurred while trying to send crash report")
except Exception: # We don't want any exception to cause problems
Logger.logException("e", "An exception occurred while trying to send crash report")

textarea.setText(crash_info)
os._exit(1)

buttons = QDialogButtonBox(QDialogButtonBox.Close, dialog)
layout.addWidget(buttons)
buttons.addButton(catalog.i18nc("@action:button", "Open Web Page"), QDialogButtonBox.HelpRole)
buttons.rejected.connect(dialog.close)
buttons.helpRequested.connect(lambda: webbrowser.open("http://github.com/Ultimaker/Cura/issues"))
def show(self):
# must run the GUI code on the Qt thread, otherwise the widgets on the dialog won't react correctly.
Application.getInstance().callLater(self._show)

dialog.exec_()
sys.exit(1)
def _show(self):
self.dialog.exec_()
os._exit(1)

+ 61
- 8
cura/CuraApplication.py View File

@@ -51,6 +51,7 @@ from cura.Settings.MaterialsModel import MaterialsModel
from cura.Settings.QualityAndUserProfilesModel import QualityAndUserProfilesModel
from cura.Settings.SettingInheritanceManager import SettingInheritanceManager
from cura.Settings.UserProfilesModel import UserProfilesModel
from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager

import time

@@ -107,7 +108,7 @@ class CuraApplication(QtApplication):
# SettingVersion represents the set of settings available in the machine/extruder definitions.
# You need to make sure that this version number needs to be increased if there is any non-backwards-compatible
# changes of the settings.
SettingVersion = 3
SettingVersion = 4

class ResourceTypes:
QmlFiles = Resources.UserType + 1
@@ -128,6 +129,8 @@ class CuraApplication(QtApplication):
# Cura will always show the Add Machine Dialog upon start.
stacksValidationFinished = pyqtSignal() # Emitted whenever a validation is finished

projectFileLoaded = pyqtSignal(str) # Emitted whenever a project file is loaded

def __init__(self):
# this list of dir names will be used by UM to detect an old cura directory
for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "user", "variants"]:
@@ -216,6 +219,7 @@ class CuraApplication(QtApplication):
self._machine_manager = None # This is initialized on demand.
self._material_manager = None
self._setting_inheritance_manager = None
self._simple_mode_settings_manager = None

self._additional_components = {} # Components to add to certain areas in the interface

@@ -230,6 +234,7 @@ class CuraApplication(QtApplication):

self.setRequiredPlugins([
"CuraEngineBackend",
"UserAgreement",
"SolidView",
"LayerView",
"STLReader",
@@ -287,8 +292,9 @@ class CuraApplication(QtApplication):
empty_quality_container = copy.deepcopy(empty_container)
empty_quality_container._id = "empty_quality"
empty_quality_container.setName("Not Supported")
empty_quality_container.addMetaDataEntry("quality_type", "normal")
empty_quality_container.addMetaDataEntry("quality_type", "not_supported")
empty_quality_container.addMetaDataEntry("type", "quality")
empty_quality_container.addMetaDataEntry("supported", False)
ContainerRegistry.getInstance().addContainer(empty_quality_container)
empty_quality_changes_container = copy.deepcopy(empty_container)
empty_quality_changes_container._id = "empty_quality_changes"
@@ -321,6 +327,8 @@ class CuraApplication(QtApplication):
preferences.addPreference("view/invert_zoom", False)

Preferences.getInstance().addPreference("cura/recent_files", "")
self._need_to_show_user_agreement = not Preferences.getInstance().getValue("general/accepted_user_agreement")


for key in [
"dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin
@@ -416,6 +424,14 @@ class CuraApplication(QtApplication):
def _onEngineCreated(self):
self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())

@pyqtProperty(bool)
def needToShowUserAgreement(self):
return self._need_to_show_user_agreement


def setNeedToShowUserAgreement(self, set_value = True):
self._need_to_show_user_agreement = set_value

## The "Quit" button click event handler.
@pyqtSlot()
def closeApplication(self):
@@ -434,6 +450,7 @@ class CuraApplication(QtApplication):
showDiscardOrKeepProfileChanges = pyqtSignal()

def discardOrKeepProfileChanges(self):
has_user_interaction = False
choice = Preferences.getInstance().getValue("cura/choice_on_profile_override")
if choice == "always_discard":
# don't show dialog and DISCARD the profile
@@ -444,6 +461,10 @@ class CuraApplication(QtApplication):
elif len(self.getGlobalContainerStack().getTop().getAllKeys()) > 0:
# ALWAYS ask whether to keep or discard the profile
self.showDiscardOrKeepProfileChanges.emit()
has_user_interaction = True
return has_user_interaction

onDiscardOrKeepProfileChangesClosed = pyqtSignal() # Used to notify other managers that the dialog was closed

@pyqtSlot(str)
def discardOrKeepProfileChangesClosed(self, option):
@@ -451,9 +472,25 @@ class CuraApplication(QtApplication):
global_stack = self.getGlobalContainerStack()
for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()):
extruder.getTop().clear()

global_stack.getTop().clear()

# if the user decided to keep settings then the user settings should be re-calculated and validated for errors
# before slicing. To ensure that slicer uses right settings values
elif option == "keep":
global_stack = self.getGlobalContainerStack()
for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()):
user_extruder_container = extruder.getTop()
if user_extruder_container:
user_extruder_container.update()

user_global_container = global_stack.getTop()
if user_global_container:
user_global_container.update()

# notify listeners that quality has changed (after user selected discard or keep)
self.onDiscardOrKeepProfileChangesClosed.emit()
self.getMachineManager().activeQualityChanged.emit()

@pyqtSlot(int)
def messageBoxClosed(self, button):
if self._message_box_callback:
@@ -581,6 +618,7 @@ class CuraApplication(QtApplication):
super().addCommandLineOptions(parser)
parser.add_argument("file", nargs="*", help="Files to load after starting the application.")
parser.add_argument("--single-instance", action="store_true", default=False)
parser.add_argument("--headless", action = "store_true", default=False)

# Set up a local socket server which listener which coordinates single instances Curas and accepts commands.
def _setUpSingleInstanceServer(self):
@@ -722,18 +760,23 @@ class CuraApplication(QtApplication):
qmlRegisterSingletonType(MachineManager, "Cura", 1, 0, "MachineManager", self.getMachineManager)
qmlRegisterSingletonType(MaterialManager, "Cura", 1, 0, "MaterialManager", self.getMaterialManager)
qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager",
self.getSettingInheritanceManager)
self.getSettingInheritanceManager)
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 2, "SimpleModeSettingsManager",
self.getSimpleModeSettingsManager)

qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
self.setMainQml(Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml"))
self._qml_import_paths.append(Resources.getPath(self.ResourceTypes.QmlFiles))
self.initializeEngine()

if self._engine.rootObjects:
run_headless = self.getCommandLineOption("headless", False)
if not run_headless:
self.initializeEngine()

if run_headless or self._engine.rootObjects:
self.closeSplash()

for file in self.getCommandLineOption("file", []):
self._openFile(file)
for file_name in self.getCommandLineOption("file", []):
self._openFile(file_name)
for file_name in self._open_file_queue: #Open all the files that were queued up while plug-ins were loading.
self._openFile(file_name)

@@ -793,6 +836,11 @@ class CuraApplication(QtApplication):
def getMachineActionManager(self, *args):
return self._machine_action_manager

def getSimpleModeSettingsManager(self, *args):
if self._simple_mode_settings_manager is None:
self._simple_mode_settings_manager = SimpleModeSettingsManager()
return self._simple_mode_settings_manager

## Handle Qt events
def event(self, event):
if event.type() == QEvent.FileOpen:
@@ -1314,6 +1362,9 @@ class CuraApplication(QtApplication):
# see GroupDecorator._onChildrenChanged

def _createSplashScreen(self):
run_headless = self.getCommandLineOption("headless", False)
if run_headless:
return None
return CuraSplashScreen.CuraSplashScreen()

def _onActiveMachineChanged(self):
@@ -1487,6 +1538,8 @@ class CuraApplication(QtApplication):

# If a model is to small then it will not contain any points
if offset_shape_arr is None and hull_shape_arr is None:
Message(self._i18n_catalog.i18nc("@info:status", "The selected model was too small to load."),
title=self._i18n_catalog.i18nc("@info:title", "Warning")).show()
return

# Step is for skipping tests to make it a lot faster. it also makes the outcome somewhat rougher

+ 1
- 1
cura/CuraSplashScreen.py View File

@@ -67,7 +67,7 @@ class CuraSplashScreen(QSplashScreen):
#painter.drawText(0, 0, 330 * self._scale, 230 * self._scale, Qt.AlignHCenter | Qt.AlignBottom, version[0])
painter.drawText(380, 200, 330 * self._scale, 230 * self._scale, Qt.AlignHCenter | Qt.AlignBottom, version[0])
if len(version) > 1:
font.setPointSize(12)
font.setPixelSize(16)
painter.setFont(font)
painter.drawText(380, 220, 330 * self._scale, 265 * self._scale, Qt.AlignHCenter | Qt.AlignBottom, self._current_message)
#painter.drawText(0, 0, 330 * self._scale, 265 * self._scale, Qt.AlignHCenter | Qt.AlignBottom, version[1])

+ 57
- 0
cura/PreviewPass.py View File

@@ -0,0 +1,57 @@
# Copyright (c) 2017 Ultimaker B.V.
# Uranium is released under the terms of the LGPLv3 or higher.

from UM.Application import Application
from UM.Resources import Resources

from UM.View.RenderPass import RenderPass
from UM.View.GL.OpenGL import OpenGL
from UM.View.RenderBatch import RenderBatch


from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator

from typing import Optional

MYPY = False
if MYPY:
from UM.Scene.Camera import Camera

## A render pass subclass that renders slicable objects with default parameters.
# It uses the active camera by default, but it can be overridden to use a different camera.
#
# This is useful to get a preview image of a scene taken from a different location as the active camera.
class PreviewPass(RenderPass):
def __init__(self, width: int, height: int):
super().__init__("preview", width, height, 0)

self._camera = None # type: Optional[Camera]

self._renderer = Application.getInstance().getRenderer()

self._shader = None
self._scene = Application.getInstance().getController().getScene()

# Set the camera to be used by this render pass
# if it's None, the active camera is used
def setCamera(self, camera: Optional["Camera"]):
self._camera = camera

def render(self) -> None:
if not self._shader:
self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "object.shader"))

# Create a new batch to be rendered
batch = RenderBatch(self._shader)

# Fill up the batch with objects that can be sliced. `
for node in DepthFirstIterator(self._scene.getRoot()):
if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible():
batch.addItem(node.getWorldTransformation(), node.getMeshData())

self.bind()
if self._camera is None:
batch.render(Application.getInstance().getController().getScene().getActiveCamera())
else:
batch.render(self._camera)
self.release()

+ 39
- 7
cura/PrintInformation.py View File

@@ -59,6 +59,7 @@ class PrintInformation(QObject):
self._material_lengths = []
self._material_weights = []
self._material_costs = []
self._material_names = []

self._pre_sliced = False

@@ -71,10 +72,11 @@ class PrintInformation(QObject):
self._base_name = ""
self._abbr_machine = ""
self._job_name = ""
self._project_name = ""

Application.getInstance().globalContainerStackChanged.connect(self._updateJobName)
Application.getInstance().fileLoaded.connect(self.setBaseName)
Application.getInstance().projectFileLoaded.connect(self.setProjectName)
Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged)

self._active_material_container = None
@@ -83,7 +85,6 @@ class PrintInformation(QObject):

self._material_amounts = []


# Crate cura message translations and using translation keys initialize empty time Duration object for total time
# and time for each feature
def initializeCuraMessagePrintTimeProperties(self):
@@ -144,6 +145,12 @@ class PrintInformation(QObject):
def materialCosts(self):
return self._material_costs

materialNamesChanged = pyqtSignal()

@pyqtProperty("QVariantList", notify = materialNamesChanged)
def materialNames(self):
return self._material_names

def _onPrintDurationMessage(self, print_time, material_amounts):

self._updateTotalPrintTimePerFeature(print_time)
@@ -175,6 +182,7 @@ class PrintInformation(QObject):
self._material_lengths = []
self._material_weights = []
self._material_costs = []
self._material_names = []

material_preference_values = json.loads(Preferences.getInstance().getValue("cura/material_settings"))

@@ -193,8 +201,10 @@ class PrintInformation(QObject):

weight = float(amount) * float(density) / 1000
cost = 0
material_name = catalog.i18nc("@label unknown material", "Unknown")
if material:
material_guid = material.getMetaDataEntry("GUID")
material_name = material.getName()
if material_guid in material_preference_values:
material_values = material_preference_values[material_guid]

@@ -213,10 +223,12 @@ class PrintInformation(QObject):
self._material_weights.append(weight)
self._material_lengths.append(length)
self._material_costs.append(cost)
self._material_names.append(material_name)

self.materialLengthsChanged.emit()
self.materialWeightsChanged.emit()
self.materialCostsChanged.emit()
self.materialNamesChanged.emit()

def _onPreferencesChanged(self, preference):
if preference != "cura/material_settings":
@@ -246,13 +258,25 @@ class PrintInformation(QObject):
self._job_name = name
self.jobNameChanged.emit()

@pyqtSlot(str)
def setProjectName(self, name):
self._project_name = name
self.setJobName(name)

jobNameChanged = pyqtSignal()

@pyqtProperty(str, notify = jobNameChanged)
def jobName(self):
return self._job_name

def _updateJobName(self):
def _updateJobName(self, empty_name = False):
# if the project name is set, we use the project name as the job name, so the job name should not get updated
# if a model file is loaded after that.
if self._project_name != "":
if empty_name:
self._project_name = ""
return

if self._base_name == "":
self._job_name = ""
self.jobNameChanged.emit()
@@ -287,9 +311,13 @@ class PrintInformation(QObject):
name = os.path.splitext(name)[0]

# name is "" when I first had some meshes and afterwards I deleted them so the naming should start again
if name == "" or (self._base_name == "" and self._base_name != name):
is_empty = name == ""
if is_empty or (self._base_name == "" and self._base_name != name):
# remove ".curaproject" suffix from (imported) the file name
if name.endswith(".curaproject"):
name = name[:name.rfind(".curaproject")]
self._base_name = name
self._updateJobName()
self._updateJobName(empty_name = is_empty)

## Created an acronymn-like abbreviated machine name from the currently active machine name
# Called each time the global stack is switched
@@ -308,7 +336,12 @@ class PrintInformation(QObject):
elif word.isdigit():
abbr_machine += word
else:
abbr_machine += self._stripAccents(word.strip("()[]{}#").upper())[0]
stripped_word = self._stripAccents(word.strip("()[]{}#").upper())
# - use only the first character if the word is too long (> 3 characters)
# - use the whole word if it's not too long (<= 3 characters)
if len(stripped_word) > 3:
stripped_word = stripped_word[0]
abbr_machine += stripped_word

self._abbr_machine = abbr_machine

@@ -334,4 +367,3 @@ class PrintInformation(QObject):

temp_material_amounts = [0]
self._onPrintDurationMessage(temp_message, temp_material_amounts)


+ 6
- 0
cura/PrinterOutputDevice.py View File

@@ -70,6 +70,7 @@ class PrinterOutputDevice(QObject, OutputDevice):
self._can_pause = True
self._can_abort = True
self._can_pre_heat_bed = True
self._can_control_manually = True

def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None):
raise NotImplementedError("requestWrite needs to be implemented")
@@ -140,6 +141,11 @@ class PrinterOutputDevice(QObject, OutputDevice):
def canAbort(self):
return self._can_abort

# Does the printer support manual control at all
@pyqtProperty(bool, constant=True)
def canControlManually(self):
return self._can_control_manually

@pyqtProperty(QObject, constant=True)
def monitorItem(self):
# Note that we specifically only check if the monitor component is created.

+ 18
- 5
cura/QualityManager.py View File

@@ -87,7 +87,7 @@ class QualityManager:
qualities = set(quality_type_dict.values())
for material_container in material_containers[1:]:
next_quality_type_dict = self.__fetchQualityTypeDictForMaterial(machine_definition, material_container)
qualities.update(set(next_quality_type_dict.values()))
qualities.intersection_update(set(next_quality_type_dict.values()))
return list(qualities)
@@ -178,12 +178,25 @@ class QualityManager:
def findAllUsableQualitiesForMachineAndExtruders(self, global_container_stack: "GlobalStack", extruder_stacks: List["ExtruderStack"]) -> List[InstanceContainer]:
global_machine_definition = global_container_stack.getBottom()
machine_manager = Application.getInstance().getMachineManager()
active_stack_id = machine_manager.activeStackId
materials = []
# TODO: fix this
if extruder_stacks:
# Multi-extruder machine detected.
materials = [stack.material for stack in extruder_stacks]
# Multi-extruder machine detected
for stack in extruder_stacks:
if stack.getId() == active_stack_id and machine_manager.newMaterial:
materials.append(machine_manager.newMaterial)
else:
materials.append(stack.material)
else:
# Machine with one extruder.
materials = [global_container_stack.material]
# Machine with one extruder
if global_container_stack.getId() == active_stack_id and machine_manager.newMaterial:
materials.append(machine_manager.newMaterial)
else:
materials.append(global_container_stack.material)
quality_types = self.findAllQualityTypesForMachineAndMaterials(global_machine_definition, materials)

+ 3
- 0
cura/Settings/CuraContainerRegistry.py View File

@@ -303,6 +303,9 @@ class CuraContainerRegistry(ContainerRegistry):
if "material" in quality_type_criteria:
materials = ContainerRegistry.getInstance().findInstanceContainers(id = quality_type_criteria["material"])
del quality_type_criteria["material"]
# Do not filter quality containers here with materials because we are trying to import a profile, so it should
# NOT be restricted by the active materials on the current machine.
materials = None
# Check to make sure the imported profile actually makes sense in context of the current configuration.
# This prevents issues where importing a "draft" profile for a machine without "draft" qualities would report as

+ 5
- 3
cura/Settings/ExtrudersModel.py View File

@@ -4,12 +4,14 @@
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty, QTimer
from typing import Iterable

from UM.i18n import i18nCatalog
import UM.Qt.ListModel
from UM.Application import Application
import UM.FlameProfiler
from cura.Settings.ExtruderManager import ExtruderManager
from cura.Settings.ExtruderStack import ExtruderStack #To listen to changes on the extruders.
from cura.Settings.MachineManager import MachineManager #To listen to changes on the extruders of the currently active machine.

catalog = i18nCatalog("cura")

## Model that holds extruders.
#
@@ -172,7 +174,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
color = material.getMetaDataEntry("color_code", default = self.defaultColors[0]) if material else self.defaultColors[0]
item = {
"id": global_container_stack.getId(),
"name": "Global",
"name": catalog.i18nc("@menuitem", "Global"),
"color": color,
"index": -1,
"definition": ""
@@ -215,7 +217,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
if self._add_optional_extruder:
item = {
"id": "",
"name": "Not overridden",
"name": catalog.i18nc("@menuitem", "Not overridden"),
"color": "#ffffff",
"index": -1,
"definition": ""

+ 109
- 34
cura/Settings/MachineManager.py View File

@@ -40,8 +40,6 @@ if TYPE_CHECKING:
from cura.Settings.CuraContainerStack import CuraContainerStack
from cura.Settings.GlobalStack import GlobalStack

import os


class MachineManager(QObject):
def __init__(self, parent = None):
@@ -50,6 +48,11 @@ class MachineManager(QObject):
self._active_container_stack = None # type: CuraContainerStack
self._global_container_stack = None # type: GlobalStack

# Used to store the new containers until after confirming the dialog
self._new_variant_container = None
self._new_material_container = None
self._new_quality_containers = []

self._error_check_timer = QTimer()
self._error_check_timer.setInterval(250)
self._error_check_timer.setSingleShot(True)
@@ -61,6 +64,7 @@ class MachineManager(QObject):
self._instance_container_timer.timeout.connect(self.__onInstanceContainersChanged)

Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)

## When the global container is changed, active material probably needs to be updated.
self.globalContainerChanged.connect(self.activeMaterialChanged)
self.globalContainerChanged.connect(self.activeVariantChanged)
@@ -68,10 +72,10 @@ class MachineManager(QObject):

self._stacks_have_errors = None

self._empty_variant_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
self._empty_material_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
self._empty_quality_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
self._empty_quality_changes_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
self._empty_variant_container = ContainerRegistry.getInstance().findContainers(id = "empty_variant")[0]
self._empty_material_container = ContainerRegistry.getInstance().findContainers(id = "empty_material")[0]
self._empty_quality_container = ContainerRegistry.getInstance().findContainers(id = "empty_quality")[0]
self._empty_quality_changes_container = ContainerRegistry.getInstance().findContainers(id = "empty_quality_changes")[0]

self._onGlobalContainerChanged()

@@ -89,6 +93,9 @@ class MachineManager(QObject):
ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeStackChanged)
self.activeStackChanged.connect(self.activeStackValueChanged)

# when a user closed dialog check if any delayed material or variant changes need to be applied
Application.getInstance().onDiscardOrKeepProfileChangesClosed.connect(self._executeDelayedActiveContainerStackChanges)

Preferences.getInstance().addPreference("cura/active_machine", "")

self._global_event_keys = set()
@@ -114,7 +121,7 @@ class MachineManager(QObject):
"The selected material is incompatible with the selected machine or configuration."),
title = catalog.i18nc("@info:title", "Incompatible Material"))

globalContainerChanged = pyqtSignal() # Emitted whenever the global stack is changed (ie: when changing between printers, changing a global profile, but not when changing a value)
globalContainerChanged = pyqtSignal() # Emitted whenever the global stack is changed (ie: when changing between printers, changing a global profile, but not when changing a value)
activeMaterialChanged = pyqtSignal()
activeVariantChanged = pyqtSignal()
activeQualityChanged = pyqtSignal()
@@ -175,6 +182,14 @@ class MachineManager(QObject):
categories_list.append(category)
return categories_list

@property
def newVariant(self):
return self._new_variant_container

@property
def newMaterial(self):
return self._new_material_container

@pyqtProperty("QVariantList", notify = outputDevicesChanged)
def printerOutputDevices(self):
return self._printer_output_devices
@@ -369,6 +384,7 @@ class MachineManager(QObject):
self.activeQualityChanged.emit()
self.activeVariantChanged.emit()
self.activeMaterialChanged.emit()
self._updateStacksHaveErrors() # Prevents unwanted re-slices after changing machine
self._error_check_timer.start()

def _onInstanceContainersChanged(self, container):
@@ -385,6 +401,8 @@ class MachineManager(QObject):
@pyqtSlot(str)
def setActiveMachine(self, stack_id: str) -> None:
self.blurSettings.emit() # Ensure no-one has focus.
self._cancelDelayedActiveContainerStackChanges()

containers = ContainerRegistry.getInstance().findContainerStacks(id = stack_id)
if containers:
Application.getInstance().setGlobalContainerStack(containers[0])
@@ -867,7 +885,7 @@ class MachineManager(QObject):
self.blurSettings.emit()
old_material.nameChanged.disconnect(self._onMaterialNameChanged)

self._active_container_stack.material = material_container
self._new_material_container = material_container # self._active_container_stack will be updated with a delay
Logger.log("d", "Active material changed")
self.activeMaterialChanged.emit()

@@ -922,13 +940,13 @@ class MachineManager(QObject):
old_material = self._active_container_stack.material
if old_variant:
self.blurSettings.emit()
self._active_container_stack.variant = containers[0]
self._new_variant_container = containers[0] # self._active_container_stack will be updated with a delay
Logger.log("d", "Active variant changed to {active_variant_id}".format(active_variant_id = containers[0].getId()))
preferred_material_name = None
if old_material:
preferred_material_name = old_material.getName()
self.setActiveMaterial(self._updateMaterialContainer(self._global_container_stack.getBottom(), self._global_container_stack, containers[0], preferred_material_name).id)
preferred_material_id = self._updateMaterialContainer(self._global_container_stack.getBottom(), self._global_container_stack, containers[0], preferred_material_name).id
self.setActiveMaterial(preferred_material_id)
else:
Logger.log("w", "While trying to set the active variant, no variant was found to replace.")

@@ -943,8 +961,6 @@ class MachineManager(QObject):
if not containers or not self._global_container_stack:
return

Logger.log("d", "Attempting to change the active quality to %s", quality_id)

# Quality profile come in two flavours: type=quality and type=quality_changes
# If we found a quality_changes profile then look up its parent quality profile.
container_type = containers[0].getMetaDataEntry("type")
@@ -964,31 +980,79 @@ class MachineManager(QObject):
if new_quality_settings_list is None:
return

name_changed_connect_stacks = [] # Connect these stacks to the name changed callback
# check if any of the stacks have a not supported profile
# if that is the case, all stacks should have a not supported state (otherwise it will show quality_type normal)
has_not_supported_quality = False

# check all stacks for not supported
for setting_info in new_quality_settings_list:
if setting_info["quality"].getMetaDataEntry("quality_type") == "not_supported":
has_not_supported_quality = True
break

# set all stacks to not supported if that's the case
if has_not_supported_quality:
for setting_info in new_quality_settings_list:
setting_info["quality"] = self._empty_quality_container

self._new_quality_containers.clear()

# store the upcoming quality profile changes per stack for later execution
# this prevents re-slicing before the user has made a choice in the discard or keep dialog
# (see _executeDelayedActiveContainerStackChanges)
for setting_info in new_quality_settings_list:
stack = setting_info["stack"]
stack_quality = setting_info["quality"]
stack_quality_changes = setting_info["quality_changes"]

name_changed_connect_stacks.append(stack_quality)
name_changed_connect_stacks.append(stack_quality_changes)
self._replaceQualityOrQualityChangesInStack(stack, stack_quality, postpone_emit=True)
self._replaceQualityOrQualityChangesInStack(stack, stack_quality_changes, postpone_emit=True)

# Send emits that are postponed in replaceContainer.
# Here the stacks are finished replacing and every value can be resolved based on the current state.
for setting_info in new_quality_settings_list:
setting_info["stack"].sendPostponedEmits()
self._new_quality_containers.append({
"stack": stack,
"quality": stack_quality,
"quality_changes": stack_quality_changes
})

self._askUserToKeepOrClearCurrentSettings()
# Connect to onQualityNameChanged
for stack in name_changed_connect_stacks:
stack.nameChanged.connect(self._onQualityNameChanged)
has_user_interaction = False

if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1:
self._askUserToKeepOrClearCurrentSettings()

self.activeQualityChanged.emit()
# Show the keep/discard user settings dialog
has_user_interaction = Application.getInstance().discardOrKeepProfileChanges()

# If there is no interaction with the user (it means the dialog showing "keep" or "discard" was not shown)
# because either there are not user changes or because the used already decided to always keep or discard,
# then the quality instance container is replaced, in which case, the activeQualityChanged signal is emitted.
if not has_user_interaction:
self._executeDelayedActiveContainerStackChanges()

## Used to update material and variant in the active container stack with a delay.
# This delay prevents the stack from triggering a lot of signals (eventually resulting in slicing)
# before the user decided to keep or discard any of their changes using the dialog.
# The Application.onDiscardOrKeepProfileChangesClosed signal triggers this method.
def _executeDelayedActiveContainerStackChanges(self):
if self._new_variant_container is not None:
self._active_container_stack.variant = self._new_variant_container
self._new_variant_container = None

if self._new_material_container is not None:
self._active_container_stack.material = self._new_material_container
self._new_material_container = None

# apply the new quality to all stacks
if self._new_quality_containers:
for new_quality in self._new_quality_containers:
self._replaceQualityOrQualityChangesInStack(new_quality["stack"], new_quality["quality"], postpone_emit = True)
self._replaceQualityOrQualityChangesInStack(new_quality["stack"], new_quality["quality_changes"], postpone_emit = True)

for new_quality in self._new_quality_containers:
new_quality["stack"].nameChanged.connect(self._onQualityNameChanged)
new_quality["stack"].sendPostponedEmits() # Send the signals that were postponed in _replaceQualityOrQualityChangesInStack

self._new_quality_containers.clear()

## Cancel set changes for material and variant in the active container stack.
# Used for ignoring any changes when switching between printers (setActiveMachine)
def _cancelDelayedActiveContainerStackChanges(self):
self._new_material_container = None
self._new_variant_container = None

## Determine the quality and quality changes settings for the current machine for a quality name.
#
@@ -1008,8 +1072,14 @@ class MachineManager(QObject):
stack = extruder_stack if extruder_stack else global_container_stack
if stack:
material = stack.material

# TODO: fix this
if self._new_material_container and stack.getId() == self._active_container_stack.getId():
material = self._new_material_container

quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material])
if not quality: #No quality profile is found for this quality type.
if not quality:
# No quality profile is found for this quality type.
quality = self._empty_quality_container
result.append({"stack": stack, "quality": quality, "quality_changes": empty_quality_changes})

@@ -1044,8 +1114,12 @@ class MachineManager(QObject):
else:
Logger.log("e", "Could not find the global quality changes container with name %s", quality_changes_name)
return None

material = global_container_stack.material

if self._new_material_container and self._active_container_stack.getId() == global_container_stack.getId():
material = self._new_material_container

# For the global stack, find a quality which matches the quality_type in
# the quality changes profile and also satisfies any material constraints.
quality_type = global_quality_changes.getMetaDataEntry("quality_type")
@@ -1071,6 +1145,10 @@ class MachineManager(QObject):
quality_changes = self._empty_quality_changes_container

material = stack.material

if self._new_material_container and self._active_container_stack.getId() == stack.getId():
material = self._new_material_container

quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material])
if not quality: #No quality profile found for this quality type.
quality = self._empty_quality_container
@@ -1102,9 +1180,6 @@ class MachineManager(QObject):
stack.qualityChanges.nameChanged.connect(self._onQualityNameChanged)
self._onQualityNameChanged()

def _askUserToKeepOrClearCurrentSettings(self):
Application.getInstance().discardOrKeepProfileChanges()

@pyqtProperty(str, notify = activeVariantChanged)
def activeVariantName(self) -> str:
if self._active_container_stack:

+ 28
- 8
cura/Settings/ProfilesModel.py View File

@@ -61,6 +61,7 @@ class ProfilesModel(InstanceContainersModel):
active_extruder = extruder_manager.getActiveExtruderStack()
extruder_stacks = extruder_manager.getActiveExtruderStacks()
materials = [global_container_stack.material]
if active_extruder in extruder_stacks:
extruder_stacks.remove(active_extruder)
extruder_stacks = [active_extruder] + extruder_stacks
@@ -83,21 +84,30 @@ class ProfilesModel(InstanceContainersModel):
if quality.getMetaDataEntry("quality_type") not in quality_type_set:
result.append(quality)
# if still profiles are found, add a single empty_quality ("Not supported") instance to the drop down list
if len(result) == 0:
# If not qualities are found we dynamically create a not supported container for this machine + material combination
not_supported_container = ContainerRegistry.getInstance().findContainers(id = "empty_quality")[0]
result.append(not_supported_container)
return result
## Re-computes the items in this model, and adds the layer height role.
def _recomputeItems(self):
#Some globals that we can re-use.
# Some globals that we can re-use.
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack is None:
return
# Detecting if the machine has multiple extrusion
multiple_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1
# Get the list of extruders and place the selected extruder at the front of the list.
extruder_manager = ExtruderManager.getInstance()
active_extruder = extruder_manager.getActiveExtruderStack()
extruder_stacks = extruder_manager.getActiveExtruderStacks()
if multiple_extrusion:
# Place the active extruder at the front of the list.
# This is a workaround checking if there is an active_extruder or not before moving it to the front of the list.
@@ -111,8 +121,7 @@ class ProfilesModel(InstanceContainersModel):
extruder_stacks = new_extruder_stacks + extruder_stacks
# Get a list of usable/available qualities for this machine and material
qualities = QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_container_stack,
extruder_stacks)
qualities = QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks)
container_registry = ContainerRegistry.getInstance()
machine_manager = Application.getInstance().getMachineManager()
@@ -155,14 +164,24 @@ class ProfilesModel(InstanceContainersModel):
# Now all the containers are set
for item in containers:
profile = container_registry.findContainers(id=item["id"])
profile = container_registry.findContainers(id = item["id"])
# When for some reason there is no profile container in the registry
if not profile:
self._setItemLayerHeight(item, "", unit)
self._setItemLayerHeight(item, "", "")
item["available"] = False
yield item
continue
profile = profile[0]
# When there is a profile but it's an empty quality should. It's shown in the list (they are "Not Supported" profiles)
if profile.getId() == "empty_quality":
self._setItemLayerHeight(item, "", "")
item["available"] = True
yield item
continue
item["available"] = profile in qualities
# Easy case: This profile defines its own layer height.
@@ -179,9 +198,10 @@ class ProfilesModel(InstanceContainersModel):
if quality_result["stack"] is global_container_stack:
quality = quality_result["quality"]
break
else: #No global container stack in the results:
else:
# No global container stack in the results:
if quality_results:
quality = quality_results[0]["quality"] #Take any of the extruders.
quality = quality_results[0]["quality"] # Take any of the extruders.
else:
quality = None
if quality and quality.hasProperty("layer_height", "value"):
@@ -189,7 +209,7 @@ class ProfilesModel(InstanceContainersModel):
yield item
continue
#Quality has no value for layer height either. Get the layer height from somewhere lower in the stack.
# Quality has no value for layer height either. Get the layer height from somewhere lower in the stack.
skip_until_container = global_container_stack.material
if not skip_until_container or skip_until_container == ContainerRegistry.getInstance().getEmptyInstanceContainer(): #No material in stack.
skip_until_container = global_container_stack.variant

+ 92
- 0
cura/Settings/SimpleModeSettingsManager.py View File

@@ -0,0 +1,92 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.

from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty

from UM.Application import Application


class SimpleModeSettingsManager(QObject):

def __init__(self, parent = None):
super().__init__(parent)

self._machine_manager = Application.getInstance().getMachineManager()
self._is_profile_customized = False # True when default profile has user changes
self._is_profile_user_created = False # True when profile was custom created by user

self._machine_manager.activeStackValueChanged.connect(self._updateIsProfileCustomized)
self._machine_manager.activeQualityChanged.connect(self._updateIsProfileUserCreated)

# update on create as the activeQualityChanged signal is emitted before this manager is created when Cura starts
self._updateIsProfileCustomized()
self._updateIsProfileUserCreated()

isProfileCustomizedChanged = pyqtSignal()
isProfileUserCreatedChanged = pyqtSignal()

@pyqtProperty(bool, notify = isProfileCustomizedChanged)
def isProfileCustomized(self):
return self._is_profile_customized

def _updateIsProfileCustomized(self):
user_setting_keys = set()

if not self._machine_manager.activeMachine:
return False

global_stack = self._machine_manager.activeMachine

# check user settings in the global stack
user_setting_keys.update(set(global_stack.userChanges.getAllKeys()))

# check user settings in the extruder stacks
if global_stack.extruders:
for extruder_stack in global_stack.extruders.values():
user_setting_keys.update(set(extruder_stack.userChanges.getAllKeys()))

# remove settings that are visible in recommended (we don't show the reset button for those)
for skip_key in self.__ignored_custom_setting_keys:
if skip_key in user_setting_keys:
user_setting_keys.remove(skip_key)

has_customized_user_settings = len(user_setting_keys) > 0

if has_customized_user_settings != self._is_profile_customized:
self._is_profile_customized = has_customized_user_settings
self.isProfileCustomizedChanged.emit()

@pyqtProperty(bool, notify = isProfileUserCreatedChanged)
def isProfileUserCreated(self):
return self._is_profile_user_created

def _updateIsProfileUserCreated(self):
quality_changes_keys = set()

if not self._machine_manager.activeMachine:
return False

global_stack = self._machine_manager.activeMachine

# check quality changes settings in the global stack
quality_changes_keys.update(set(global_stack.qualityChanges.getAllKeys()))

# check quality changes settings in the extruder stacks
if global_stack.extruders:
for extruder_stack in global_stack.extruders.values():
quality_changes_keys.update(set(extruder_stack.qualityChanges.getAllKeys()))

# check if the qualityChanges container is not empty (meaning it is a user created profile)
has_quality_changes = len(quality_changes_keys) > 0

if has_quality_changes != self._is_profile_user_created:
self._is_profile_user_created = has_quality_changes
self.isProfileUserCreatedChanged.emit()

# These are the settings included in the Simple ("Recommended") Mode, so only when the other settings have been
# changed, we consider it as a user customized profile in the Simple ("Recommended") Mode.
__ignored_custom_setting_keys = ["support_enable",
"infill_sparse_density",
"gradual_infill_steps",
"adhesion_type",
"support_extruder_nr"]

+ 3
- 2
cura_app.py View File

@@ -41,8 +41,9 @@ if "PYTHONPATH" in os.environ.keys(): # If PYTHONPATH is u
sys.path.insert(1, PATH_real) # Insert it at 1 after os.curdir, which is 0.

def exceptHook(hook_type, value, traceback):
import cura.CrashHandler
cura.CrashHandler.show(hook_type, value, traceback)
from cura.CrashHandler import CrashHandler
_crash_handler = CrashHandler(hook_type, value, traceback)
_crash_handler.show()

sys.excepthook = exceptHook


+ 40
- 3
plugins/3MFReader/ThreeMFWorkspaceReader.py View File

@@ -21,11 +21,14 @@ from cura.Settings.CuraStackBuilder import CuraStackBuilder
from cura.Settings.ExtruderManager import ExtruderManager
from cura.Settings.ExtruderStack import ExtruderStack
from cura.Settings.GlobalStack import GlobalStack
from cura.Settings.CuraContainerStack import _ContainerIndexes
from cura.QualityManager import QualityManager

from configparser import ConfigParser
import zipfile
import io
import configparser
import os

i18n_catalog = i18nCatalog("cura")

@@ -302,11 +305,14 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
break

num_visible_settings = 0
has_visible_settings_string = False
try:
temp_preferences = Preferences()
temp_preferences.readFromFile(io.TextIOWrapper(archive.open("Cura/preferences.cfg"))) # We need to wrap it, else the archive parser breaks.
serialized = archive.open("Cura/preferences.cfg").read().decode("utf-8")
temp_preferences.deserialize(serialized)

visible_settings_string = temp_preferences.getValue("general/visible_settings")
has_visible_settings_string = visible_settings_string is not None
if visible_settings_string is not None:
num_visible_settings = len(visible_settings_string.split(";"))
active_mode = temp_preferences.getValue("cura/active_mode")
@@ -333,6 +339,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
self._dialog.setQualityChangesConflict(quality_changes_conflict)
self._dialog.setDefinitionChangesConflict(definition_changes_conflict)
self._dialog.setMaterialConflict(material_conflict)
self._dialog.setHasVisibleSettingsField(has_visible_settings_string)
self._dialog.setNumVisibleSettings(num_visible_settings)
self._dialog.setQualityName(quality_name)
self._dialog.setQualityType(quality_type)
@@ -407,7 +414,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# Create a shadow copy of the preferences (we don't want all of the preferences, but we do want to re-use its
# parsing code.
temp_preferences = Preferences()
temp_preferences.readFromFile(io.TextIOWrapper(archive.open("Cura/preferences.cfg"))) # We need to wrap it, else the archive parser breaks.
serialized = archive.open("Cura/preferences.cfg").read().decode("utf-8")
temp_preferences.deserialize(serialized)

# Copy a number of settings from the temp preferences to the global
global_preferences = Preferences.getInstance()
@@ -751,13 +759,37 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
self._container_registry.removeContainer(container.getId())
return

# Check quality profiles to make sure that if one stack has the "not supported" quality profile,
# all others should have the same.
#
# This block code tries to fix the following problems in Cura 3.0 and earlier:
# 1. The upgrade script can rename all "Not Supported" quality profiles to "empty_quality", but it cannot fix
# the problem that the global stack the extruder stacks may have different quality profiles. The code
# below loops over all stacks and make sure that if there is one stack with "Not Supported" profile, the
# rest should also use the "Not Supported" profile.
# 2. In earlier versions (at least 2.7 and 3.0), a wrong quality profile could be assigned to a stack. For
# example, a UM3 can have a BB 0.8 variant with "aa04_pla_fast" quality profile enabled. To fix this,
# in the code below we also check the actual available quality profiles for the machine.
#
has_not_supported = False
for stack in [global_stack] + extruder_stacks:
if stack.quality.getId() == "empty_quality":
has_not_supported = True
break
if not has_not_supported:
available_quality = QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_stack, extruder_stacks)
has_not_supported = not available_quality
if has_not_supported:
empty_quality_container = self._container_registry.findInstanceContainers(id = "empty_quality")[0]
for stack in [global_stack] + extruder_stacks:
stack.replaceContainer(_ContainerIndexes.Quality, empty_quality_container)

#
# Replacing the old containers if resolve is "new".
# When resolve is "new", some containers will get renamed, so all the other containers that reference to those
# MUST get updated too.
#
if self._resolve_strategies["machine"] == "new":

# A new machine was made, but it was serialized with the wrong user container. Fix that now.
for container in user_instance_containers:
# replacing the container ID for user instance containers for the extruders
@@ -871,6 +903,11 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
nodes = self._3mf_mesh_reader.read(file_name)
if nodes is None:
nodes = []

base_file_name = os.path.basename(file_name)
if base_file_name.endswith(".curaproject.3mf"):
base_file_name = base_file_name[:base_file_name.rfind(".curaproject.3mf")]
Application.getInstance().projectFileLoaded.emit(base_file_name)
return nodes

## HACK: Replaces the material container in the given stack with a newly created material container.

+ 18
- 0
plugins/3MFReader/WorkspaceDialog.py View File

@@ -38,6 +38,7 @@ class WorkspaceDialog(QObject):
self._has_definition_changes_conflict = False
self._has_machine_conflict = False
self._has_material_conflict = False
self._has_visible_settings_field = False
self._num_visible_settings = 0
self._num_user_settings = 0
self._active_mode = ""
@@ -58,6 +59,7 @@ class WorkspaceDialog(QObject):
numVisibleSettingsChanged = pyqtSignal()
activeModeChanged = pyqtSignal()
qualityNameChanged = pyqtSignal()
hasVisibleSettingsFieldChanged = pyqtSignal()
numSettingsOverridenByQualityChangesChanged = pyqtSignal()
qualityTypeChanged = pyqtSignal()
machineNameChanged = pyqtSignal()
@@ -167,6 +169,14 @@ class WorkspaceDialog(QObject):
self._active_mode = i18n_catalog.i18nc("@title:tab", "Custom")
self.activeModeChanged.emit()

@pyqtProperty(int, notify = hasVisibleSettingsFieldChanged)
def hasVisibleSettingsField(self):
return self._has_visible_settings_field

def setHasVisibleSettingsField(self, has_visible_settings_field):
self._has_visible_settings_field = has_visible_settings_field
self.hasVisibleSettingsFieldChanged.emit()

@pyqtProperty(int, constant = True)
def totalNumberOfSettings(self):
return len(ContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")[0].getAllKeys())
@@ -285,6 +295,14 @@ class WorkspaceDialog(QObject):
except:
pass

@pyqtSlot(bool)
def _onVisibilityChanged(self, visible):
if not visible:
try:
self._lock.release()
except:
pass

@pyqtSlot()
def onOkButtonClicked(self):
self._view.hide()

+ 2
- 1
plugins/3MFReader/WorkspaceDialog.qml View File

@@ -331,6 +331,7 @@ UM.Dialog
{
width: parent.width
height: childrenRect.height
visible: manager.hasVisibleSettingsField
Label
{
text: catalog.i18nc("@action:label", "Visible settings:")
@@ -364,7 +365,7 @@ UM.Dialog
Label
{
id: warningLabel
text: catalog.i18nc("@action:warning", "Loading a project will clear all models on the buildplate")
text: catalog.i18nc("@action:warning", "Loading a project will clear all models on the build plate.")
wrapMode: Text.Wrap
}
}

+ 0
- 1
plugins/ChangeLogPlugin/ChangeLog.py View File

@@ -8,7 +8,6 @@ from UM.Application import Application
from UM.PluginRegistry import PluginRegistry
from UM.Version import Version

from PyQt5.QtQuick import QQuickView