VersionUpgrade25to26.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. # Copyright (c) 2018 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. import configparser #To parse the files we need to upgrade and write the new files.
  4. import io #To serialise configparser output to a string.
  5. import os
  6. from urllib.parse import quote_plus
  7. from UM.Resources import Resources
  8. from UM.VersionUpgrade import VersionUpgrade
  9. _removed_settings = { #Settings that were removed in 2.5.
  10. "start_layers_at_same_position",
  11. "sub_div_rad_mult"
  12. }
  13. _split_settings = { #These settings should be copied to all settings it was split into.
  14. "support_interface_line_distance": {"support_roof_line_distance", "support_bottom_line_distance"}
  15. }
  16. ## A collection of functions that convert the configuration of the user in Cura
  17. # 2.5 to a configuration for Cura 2.6.
  18. #
  19. # All of these methods are essentially stateless.
  20. class VersionUpgrade25to26(VersionUpgrade):
  21. def __init__(self):
  22. super().__init__()
  23. self._current_fdm_printer_count = 2
  24. ## Gets the version number from a CFG file in Uranium's 2.5 format.
  25. #
  26. # Since the format may change, this is implemented for the 2.5 format only
  27. # and needs to be included in the version upgrade system rather than
  28. # globally in Uranium.
  29. #
  30. # \param serialised The serialised form of a CFG file.
  31. # \return The version number stored in the CFG file.
  32. # \raises ValueError The format of the version number in the file is
  33. # incorrect.
  34. # \raises KeyError The format of the file is incorrect.
  35. def getCfgVersion(self, serialised):
  36. parser = configparser.ConfigParser(interpolation = None)
  37. parser.read_string(serialised)
  38. format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
  39. setting_version = int(parser.get("metadata", "setting_version", fallback = 0))
  40. return format_version * 1000000 + setting_version
  41. ## Upgrades the preferences file from version 2.5 to 2.6.
  42. #
  43. # \param serialised The serialised form of a preferences file.
  44. # \param filename The name of the file to upgrade.
  45. def upgradePreferences(self, serialised, filename):
  46. parser = configparser.ConfigParser(interpolation = None)
  47. parser.read_string(serialised)
  48. #Remove settings from the visible_settings.
  49. if parser.has_section("general") and "visible_settings" in parser["general"]:
  50. visible_settings = parser["general"]["visible_settings"].split(";")
  51. new_visible_settings = []
  52. for setting in visible_settings:
  53. if setting in _removed_settings:
  54. continue #Skip.
  55. if setting in _split_settings:
  56. for replaced_setting in _split_settings[setting]:
  57. new_visible_settings.append(replaced_setting)
  58. continue #Don't add the original.
  59. new_visible_settings.append(setting) #No special handling, so just add the original visible setting back.
  60. parser["general"]["visible_settings"] = ";".join(new_visible_settings)
  61. #Change the version number in the file.
  62. if "general" not in parser:
  63. parser["general"] = {}
  64. parser.set("general", "version", "4")
  65. if "metadata" not in parser:
  66. parser["metadata"] = {}
  67. parser.set("metadata", "setting_version", "1")
  68. #Re-serialise the file.
  69. output = io.StringIO()
  70. parser.write(output)
  71. return [filename], [output.getvalue()]
  72. ## Upgrades an instance container from version 2.5 to 2.6.
  73. #
  74. # \param serialised The serialised form of a quality profile.
  75. # \param filename The name of the file to upgrade.
  76. def upgradeInstanceContainer(self, serialised, filename):
  77. parser = configparser.ConfigParser(interpolation = None)
  78. parser.read_string(serialised)
  79. #Remove settings from the [values] section.
  80. if parser.has_section("values"):
  81. for removed_setting in (_removed_settings & parser["values"].keys()): #Both in keys that need to be removed and in keys present in the file.
  82. del parser["values"][removed_setting]
  83. for replaced_setting in (_split_settings.keys() & parser["values"].keys()):
  84. for replacement in _split_settings[replaced_setting]:
  85. parser["values"][replacement] = parser["values"][replaced_setting] #Copy to replacement before removing the original!
  86. del replaced_setting
  87. for each_section in ("general", "metadata"):
  88. if not parser.has_section(each_section):
  89. parser.add_section(each_section)
  90. # Update version numbers
  91. parser["general"]["version"] = "2"
  92. parser["metadata"]["setting_version"] = "1"
  93. #Re-serialise the file.
  94. output = io.StringIO()
  95. parser.write(output)
  96. return [filename], [output.getvalue()]
  97. ## Upgrades a machine stack from version 2.5 to 2.6
  98. #
  99. # \param serialised The serialised form of a quality profile.
  100. # \param filename The name of the file to upgrade.
  101. def upgradeMachineStack(self, serialised, filename):
  102. parser = configparser.ConfigParser(interpolation = None)
  103. parser.read_string(serialised)
  104. # NOTE: This is for Custom FDM printers
  105. # In 2.5, Custom FDM printers don't support multiple extruders, but since 2.6 they do.
  106. machine_id = parser["general"]["id"]
  107. quality_container_id = parser["containers"]["2"]
  108. material_container_id = parser["containers"]["3"]
  109. # we don't have definition_changes container in 2.5
  110. if "6" in parser["containers"]:
  111. definition_container_id = parser["containers"]["6"]
  112. else:
  113. definition_container_id = parser["containers"]["5"]
  114. if definition_container_id == "custom" and not self._checkCustomFdmPrinterHasExtruderStack(machine_id):
  115. # go through all extruders and make sure that this custom FDM printer has 8 extruder stacks.
  116. self._acquireNextUniqueCustomFdmPrinterExtruderStackIdIndex()
  117. for position in range(8):
  118. self._createCustomFdmPrinterExtruderStack(machine_id, position, quality_container_id, material_container_id)
  119. # Update version numbers
  120. parser["general"]["version"] = "3"
  121. parser["metadata"]["setting_version"] = "1"
  122. # Re-serialise the file.
  123. output = io.StringIO()
  124. parser.write(output)
  125. return [filename], [output.getvalue()]
  126. ## Acquires the next unique extruder stack index number for the Custom FDM Printer.
  127. def _acquireNextUniqueCustomFdmPrinterExtruderStackIdIndex(self):
  128. extruder_stack_dir = os.path.join(Resources.getDataStoragePath(), "extruders")
  129. file_name_list = os.listdir(extruder_stack_dir)
  130. file_name_list = [os.path.basename(file_name) for file_name in file_name_list]
  131. while True:
  132. self._current_fdm_printer_count += 1
  133. stack_id_exists = False
  134. for position in range(8):
  135. stack_id = "custom_extruder_%s" % (position + 1)
  136. if self._current_fdm_printer_count > 1:
  137. stack_id += " #%s" % self._current_fdm_printer_count
  138. if stack_id in file_name_list:
  139. stack_id_exists = True
  140. break
  141. if not stack_id_exists:
  142. break
  143. return self._current_fdm_printer_count
  144. def _checkCustomFdmPrinterHasExtruderStack(self, machine_id):
  145. # go through all extruders and make sure that this custom FDM printer has extruder stacks.
  146. extruder_stack_dir = os.path.join(Resources.getDataStoragePath(), "extruders")
  147. has_extruders = False
  148. for item in os.listdir(extruder_stack_dir):
  149. file_path = os.path.join(extruder_stack_dir, item)
  150. if not os.path.isfile(file_path):
  151. continue
  152. parser = configparser.ConfigParser()
  153. try:
  154. parser.read([file_path])
  155. except:
  156. # skip, it is not a valid stack file
  157. continue
  158. if "metadata" not in parser:
  159. continue
  160. if "machine" not in parser["metadata"]:
  161. continue
  162. if machine_id != parser["metadata"]["machine"]:
  163. continue
  164. has_extruders = True
  165. break
  166. return has_extruders
  167. def _createCustomFdmPrinterExtruderStack(self, machine_id: str, position: int, quality_id: str, material_id: str):
  168. stack_id = "custom_extruder_%s" % (position + 1)
  169. if self._current_fdm_printer_count > 1:
  170. stack_id += " #%s" % self._current_fdm_printer_count
  171. definition_id = "custom_extruder_%s" % (position + 1)
  172. # create a definition changes container for this stack
  173. definition_changes_parser = self._getCustomFdmPrinterDefinitionChanges(stack_id)
  174. definition_changes_id = definition_changes_parser["general"]["name"]
  175. # create a user settings container
  176. user_settings_parser = self._getCustomFdmPrinterUserSettings(stack_id)
  177. user_settings_id = user_settings_parser["general"]["name"]
  178. parser = configparser.ConfigParser()
  179. parser.add_section("general")
  180. parser["general"]["version"] = str(2)
  181. parser["general"]["name"] = "Extruder %s" % (position + 1)
  182. parser["general"]["id"] = stack_id
  183. parser.add_section("metadata")
  184. parser["metadata"]["type"] = "extruder_train"
  185. parser["metadata"]["machine"] = machine_id
  186. parser["metadata"]["position"] = str(position)
  187. parser.add_section("containers")
  188. parser["containers"]["0"] = user_settings_id
  189. parser["containers"]["1"] = "empty_quality_changes"
  190. parser["containers"]["2"] = quality_id
  191. parser["containers"]["3"] = material_id
  192. parser["containers"]["4"] = "empty_variant"
  193. parser["containers"]["5"] = definition_changes_id
  194. parser["containers"]["6"] = definition_id
  195. definition_changes_output = io.StringIO()
  196. definition_changes_parser.write(definition_changes_output)
  197. definition_changes_filename = quote_plus(definition_changes_id) + ".inst.cfg"
  198. user_settings_output = io.StringIO()
  199. user_settings_parser.write(user_settings_output)
  200. user_settings_filename = quote_plus(user_settings_id) + ".inst.cfg"
  201. extruder_output = io.StringIO()
  202. parser.write(extruder_output)
  203. extruder_filename = quote_plus(stack_id) + ".extruder.cfg"
  204. extruder_stack_dir = os.path.join(Resources.getDataStoragePath(), "extruders")
  205. definition_changes_dir = os.path.join(Resources.getDataStoragePath(), "definition_changes")
  206. user_settings_dir = os.path.join(Resources.getDataStoragePath(), "user")
  207. with open(os.path.join(definition_changes_dir, definition_changes_filename), "w", encoding = "utf-8") as f:
  208. f.write(definition_changes_output.getvalue())
  209. with open(os.path.join(user_settings_dir, user_settings_filename), "w", encoding = "utf-8") as f:
  210. f.write(user_settings_output.getvalue())
  211. with open(os.path.join(extruder_stack_dir, extruder_filename), "w", encoding = "utf-8") as f:
  212. f.write(extruder_output.getvalue())
  213. ## Creates a definition changes container which doesn't contain anything for the Custom FDM Printers.
  214. # The container ID will be automatically generated according to the given stack name.
  215. def _getCustomFdmPrinterDefinitionChanges(self, stack_id: str):
  216. # In 2.5, there is no definition_changes container for the Custom FDM printer, so it should be safe to use the
  217. # default name unless some one names the printer as something like "Custom FDM Printer_settings".
  218. definition_changes_id = stack_id + "_settings"
  219. parser = configparser.ConfigParser()
  220. parser.add_section("general")
  221. parser["general"]["version"] = str(2)
  222. parser["general"]["name"] = definition_changes_id
  223. parser["general"]["definition"] = "custom"
  224. parser.add_section("metadata")
  225. parser["metadata"]["type"] = "definition_changes"
  226. parser["metadata"]["setting_version"] = str(1)
  227. parser.add_section("values")
  228. return parser
  229. ## Creates a user settings container which doesn't contain anything for the Custom FDM Printers.
  230. # The container ID will be automatically generated according to the given stack name.
  231. def _getCustomFdmPrinterUserSettings(self, stack_id: str):
  232. # For the extruder stacks created in the upgrade, also create user_settings containers so the user changes
  233. # will be saved.
  234. user_settings_id = stack_id + "_user"
  235. parser = configparser.ConfigParser()
  236. parser.add_section("general")
  237. parser["general"]["version"] = str(2)
  238. parser["general"]["name"] = user_settings_id
  239. parser["general"]["definition"] = "custom"
  240. parser.add_section("metadata")
  241. parser["metadata"]["extruder"] = stack_id
  242. parser["metadata"]["type"] = "user"
  243. parser["metadata"]["setting_version"] = str(1)
  244. parser.add_section("values")
  245. return parser