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