ThreeMFWorkspaceReader.py 60 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056
  1. # Copyright (c) 2017 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. from UM.Workspace.WorkspaceReader import WorkspaceReader
  4. from UM.Application import Application
  5. from UM.Logger import Logger
  6. from UM.i18n import i18nCatalog
  7. from UM.Settings.ContainerStack import ContainerStack
  8. from UM.Settings.DefinitionContainer import DefinitionContainer
  9. from UM.Settings.InstanceContainer import InstanceContainer
  10. from UM.Settings.ContainerRegistry import ContainerRegistry
  11. from UM.MimeTypeDatabase import MimeTypeDatabase
  12. from UM.Job import Job
  13. from UM.Preferences import Preferences
  14. from .WorkspaceDialog import WorkspaceDialog
  15. import xml.etree.ElementTree as ET
  16. from cura.Settings.CuraStackBuilder import CuraStackBuilder
  17. from cura.Settings.ExtruderManager import ExtruderManager
  18. from cura.Settings.ExtruderStack import ExtruderStack
  19. from cura.Settings.GlobalStack import GlobalStack
  20. from cura.Settings.CuraContainerStack import _ContainerIndexes
  21. from cura.QualityManager import QualityManager
  22. from configparser import ConfigParser
  23. import zipfile
  24. import io
  25. import configparser
  26. import os
  27. i18n_catalog = i18nCatalog("cura")
  28. ## Base implementation for reading 3MF workspace files.
  29. class ThreeMFWorkspaceReader(WorkspaceReader):
  30. def __init__(self):
  31. super().__init__()
  32. self._supported_extensions = [".3mf"]
  33. self._dialog = WorkspaceDialog()
  34. self._3mf_mesh_reader = None
  35. self._container_registry = ContainerRegistry.getInstance()
  36. # suffixes registered with the MineTypes don't start with a dot '.'
  37. self._definition_container_suffix = "." + ContainerRegistry.getMimeTypeForContainer(DefinitionContainer).preferredSuffix
  38. self._material_container_suffix = None # We have to wait until all other plugins are loaded before we can set it
  39. self._instance_container_suffix = "." + ContainerRegistry.getMimeTypeForContainer(InstanceContainer).preferredSuffix
  40. self._container_stack_suffix = "." + ContainerRegistry.getMimeTypeForContainer(ContainerStack).preferredSuffix
  41. self._extruder_stack_suffix = "." + ContainerRegistry.getMimeTypeForContainer(ExtruderStack).preferredSuffix
  42. self._global_stack_suffix = "." + ContainerRegistry.getMimeTypeForContainer(GlobalStack).preferredSuffix
  43. # Certain instance container types are ignored because we make the assumption that only we make those types
  44. # of containers. They are:
  45. # - quality
  46. # - variant
  47. self._ignored_instance_container_types = {"quality", "variant"}
  48. self._resolve_strategies = {}
  49. self._id_mapping = {}
  50. # In Cura 2.5 and 2.6, the empty profiles used to have those long names
  51. self._old_empty_profile_id_dict = {"empty_%s" % k: "empty" for k in ["material", "variant"]}
  52. ## Get a unique name based on the old_id. This is different from directly calling the registry in that it caches results.
  53. # This has nothing to do with speed, but with getting consistent new naming for instances & objects.
  54. def getNewId(self, old_id):
  55. if old_id not in self._id_mapping:
  56. self._id_mapping[old_id] = self._container_registry.uniqueName(old_id)
  57. return self._id_mapping[old_id]
  58. ## Separates the given file list into a list of GlobalStack files and a list of ExtruderStack files.
  59. #
  60. # In old versions, extruder stack files have the same suffix as container stack files ".stack.cfg".
  61. #
  62. def _determineGlobalAndExtruderStackFiles(self, project_file_name, file_list):
  63. archive = zipfile.ZipFile(project_file_name, "r")
  64. global_stack_file_list = [name for name in file_list if name.endswith(self._global_stack_suffix)]
  65. extruder_stack_file_list = [name for name in file_list if name.endswith(self._extruder_stack_suffix)]
  66. # separate container stack files and extruder stack files
  67. files_to_determine = [name for name in file_list if name.endswith(self._container_stack_suffix)]
  68. for file_name in files_to_determine:
  69. # FIXME: HACK!
  70. # We need to know the type of the stack file, but we can only know it if we deserialize it.
  71. # The default ContainerStack.deserialize() will connect signals, which is not desired in this case.
  72. # Since we know that the stack files are INI files, so we directly use the ConfigParser to parse them.
  73. serialized = archive.open(file_name).read().decode("utf-8")
  74. stack_config = ConfigParser()
  75. stack_config.read_string(serialized)
  76. # sanity check
  77. if not stack_config.has_option("metadata", "type"):
  78. Logger.log("e", "%s in %s doesn't seem to be valid stack file", file_name, project_file_name)
  79. continue
  80. stack_type = stack_config.get("metadata", "type")
  81. if stack_type == "extruder_train":
  82. extruder_stack_file_list.append(file_name)
  83. elif stack_type == "machine":
  84. global_stack_file_list.append(file_name)
  85. else:
  86. Logger.log("w", "Unknown container stack type '%s' from %s in %s",
  87. stack_type, file_name, project_file_name)
  88. if len(global_stack_file_list) != 1:
  89. raise RuntimeError("More than one global stack file found: [%s]" % str(global_stack_file_list))
  90. return global_stack_file_list[0], extruder_stack_file_list
  91. ## read some info so we can make decisions
  92. # \param file_name
  93. # \param show_dialog In case we use preRead() to check if a file is a valid project file, we don't want to show a dialog.
  94. def preRead(self, file_name, show_dialog=True, *args, **kwargs):
  95. self._3mf_mesh_reader = Application.getInstance().getMeshFileHandler().getReaderForFile(file_name)
  96. if self._3mf_mesh_reader and self._3mf_mesh_reader.preRead(file_name) == WorkspaceReader.PreReadResult.accepted:
  97. pass
  98. else:
  99. Logger.log("w", "Could not find reader that was able to read the scene data for 3MF workspace")
  100. return WorkspaceReader.PreReadResult.failed
  101. machine_name = ""
  102. machine_type = ""
  103. variant_type_name = i18n_catalog.i18nc("@label", "Nozzle")
  104. # Check if there are any conflicts, so we can ask the user.
  105. archive = zipfile.ZipFile(file_name, "r")
  106. cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")]
  107. # A few lists of containers in this project files.
  108. # When loading the global stack file, it may be associated with those containers, which may or may not be
  109. # in Cura already, so we need to provide them as alternative search lists.
  110. definition_container_list = []
  111. instance_container_list = []
  112. material_container_list = []
  113. resolve_strategy_keys = ["machine", "material", "quality_changes"]
  114. self._resolve_strategies = {k: None for k in resolve_strategy_keys}
  115. containers_found_dict = {k: False for k in resolve_strategy_keys}
  116. #
  117. # Read definition containers
  118. #
  119. machine_definition_container_count = 0
  120. extruder_definition_container_count = 0
  121. definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
  122. for each_definition_container_file in definition_container_files:
  123. container_id = self._stripFileToId(each_definition_container_file)
  124. definitions = self._container_registry.findDefinitionContainers(id=container_id)
  125. if not definitions:
  126. definition_container = DefinitionContainer(container_id)
  127. definition_container.deserialize(archive.open(each_definition_container_file).read().decode("utf-8"),
  128. file_name = each_definition_container_file)
  129. else:
  130. definition_container = definitions[0]
  131. definition_container_list.append(definition_container)
  132. definition_container_type = definition_container.getMetaDataEntry("type")
  133. if definition_container_type == "machine":
  134. machine_type = definition_container.getName()
  135. variant_type_name = definition_container.getMetaDataEntry("variants_name", variant_type_name)
  136. machine_definition_container_count += 1
  137. elif definition_container_type == "extruder":
  138. extruder_definition_container_count += 1
  139. else:
  140. Logger.log("w", "Unknown definition container type %s for %s",
  141. definition_container_type, each_definition_container_file)
  142. Job.yieldThread()
  143. # sanity check
  144. if machine_definition_container_count != 1:
  145. msg = "Expecting one machine definition container but got %s" % machine_definition_container_count
  146. Logger.log("e", msg)
  147. raise RuntimeError(msg)
  148. material_labels = []
  149. material_conflict = False
  150. xml_material_profile = self._getXmlProfileClass()
  151. if self._material_container_suffix is None:
  152. self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(xml_material_profile).preferredSuffix
  153. if xml_material_profile:
  154. material_container_files = [name for name in cura_file_names if name.endswith(self._material_container_suffix)]
  155. for material_container_file in material_container_files:
  156. container_id = self._stripFileToId(material_container_file)
  157. materials = self._container_registry.findInstanceContainers(id=container_id)
  158. material_labels.append(self._getMaterialLabelFromSerialized(archive.open(material_container_file).read().decode("utf-8")))
  159. if materials:
  160. containers_found_dict["material"] = True
  161. if not materials[0].isReadOnly(): # Only non readonly materials can be in conflict
  162. material_conflict = True
  163. Job.yieldThread()
  164. # Check if any quality_changes instance container is in conflict.
  165. instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
  166. quality_name = ""
  167. quality_type = ""
  168. num_settings_overriden_by_quality_changes = 0 # How many settings are changed by the quality changes
  169. num_settings_overriden_by_definition_changes = 0 # How many settings are changed by the definition changes
  170. num_user_settings = 0
  171. quality_changes_conflict = False
  172. definition_changes_conflict = False
  173. for each_instance_container_file in instance_container_files:
  174. container_id = self._stripFileToId(each_instance_container_file)
  175. instance_container = InstanceContainer(container_id)
  176. # Deserialize InstanceContainer by converting read data from bytes to string
  177. instance_container.deserialize(archive.open(each_instance_container_file).read().decode("utf-8"),
  178. file_name = each_instance_container_file)
  179. instance_container_list.append(instance_container)
  180. container_type = instance_container.getMetaDataEntry("type")
  181. if container_type == "quality_changes":
  182. quality_name = instance_container.getName()
  183. num_settings_overriden_by_quality_changes += len(instance_container._instances)
  184. # Check if quality changes already exists.
  185. quality_changes = self._container_registry.findInstanceContainers(id = container_id)
  186. if quality_changes:
  187. containers_found_dict["quality_changes"] = True
  188. # Check if there really is a conflict by comparing the values
  189. if quality_changes[0] != instance_container:
  190. quality_changes_conflict = True
  191. elif container_type == "definition_changes":
  192. definition_name = instance_container.getName()
  193. num_settings_overriden_by_definition_changes += len(instance_container._instances)
  194. # Check if definition changes already exists.
  195. definition_changes = self._container_registry.findInstanceContainers(id = container_id)
  196. # Check if there is any difference the loaded settings from the project file and the settings in Cura.
  197. if definition_changes:
  198. containers_found_dict["definition_changes"] = True
  199. # Check if there really is a conflict by comparing the values
  200. if definition_changes[0] != instance_container:
  201. definition_changes_conflict = True
  202. elif container_type == "quality":
  203. if not quality_name:
  204. quality_name = instance_container.getName()
  205. elif container_type == "user":
  206. num_user_settings += len(instance_container._instances)
  207. elif container_type in self._ignored_instance_container_types:
  208. # Ignore certain instance container types
  209. Logger.log("w", "Ignoring instance container [%s] with type [%s]", container_id, container_type)
  210. continue
  211. Job.yieldThread()
  212. # Load ContainerStack files and ExtruderStack files
  213. global_stack_file, extruder_stack_files = self._determineGlobalAndExtruderStackFiles(
  214. file_name, cura_file_names)
  215. machine_conflict = False
  216. # Because there can be cases as follows:
  217. # - the global stack exists but some/all of the extruder stacks DON'T exist
  218. # - the global stack DOESN'T exist but some/all of the extruder stacks exist
  219. # To simplify this, only check if the global stack exists or not
  220. container_id = self._stripFileToId(global_stack_file)
  221. serialized = archive.open(global_stack_file).read().decode("utf-8")
  222. machine_name = self._getMachineNameFromSerializedStack(serialized)
  223. stacks = self._container_registry.findContainerStacks(id = container_id)
  224. if stacks:
  225. global_stack = stacks[0]
  226. containers_found_dict["machine"] = True
  227. # Check if there are any changes at all in any of the container stacks.
  228. id_list = self._getContainerIdListFromSerialized(serialized)
  229. for index, container_id in enumerate(id_list):
  230. # take into account the old empty container IDs
  231. container_id = self._old_empty_profile_id_dict.get(container_id, container_id)
  232. if global_stack.getContainer(index).getId() != container_id:
  233. machine_conflict = True
  234. break
  235. Job.yieldThread()
  236. # if the global stack is found, we check if there are conflicts in the extruder stacks
  237. if containers_found_dict["machine"] and not machine_conflict:
  238. for extruder_stack_file in extruder_stack_files:
  239. container_id = self._stripFileToId(extruder_stack_file)
  240. serialized = archive.open(extruder_stack_file).read().decode("utf-8")
  241. parser = configparser.ConfigParser()
  242. parser.read_string(serialized)
  243. # The check should be done for the extruder stack that's associated with the existing global stack,
  244. # and those extruder stacks may have different IDs.
  245. # So we check according to the positions
  246. position = str(parser["metadata"]["position"])
  247. if position not in global_stack.extruders:
  248. # The extruder position defined in the project doesn't exist in this global stack.
  249. # We can say that it is a machine conflict, but it is very hard to override the machine in this
  250. # case because we need to override the existing extruders and add the non-existing extruders.
  251. #
  252. # HACK:
  253. # To make this simple, we simply say that there is no machine conflict and create a new machine
  254. # by default.
  255. machine_conflict = False
  256. break
  257. existing_extruder_stack = global_stack.extruders[position]
  258. # check if there are any changes at all in any of the container stacks.
  259. id_list = self._getContainerIdListFromSerialized(serialized)
  260. for index, container_id in enumerate(id_list):
  261. # take into account the old empty container IDs
  262. container_id = self._old_empty_profile_id_dict.get(container_id, container_id)
  263. if existing_extruder_stack.getContainer(index).getId() != container_id:
  264. machine_conflict = True
  265. break
  266. num_visible_settings = 0
  267. has_visible_settings_string = False
  268. try:
  269. temp_preferences = Preferences()
  270. serialized = archive.open("Cura/preferences.cfg").read().decode("utf-8")
  271. temp_preferences.deserialize(serialized)
  272. visible_settings_string = temp_preferences.getValue("general/visible_settings")
  273. has_visible_settings_string = visible_settings_string is not None
  274. if visible_settings_string is not None:
  275. num_visible_settings = len(visible_settings_string.split(";"))
  276. active_mode = temp_preferences.getValue("cura/active_mode")
  277. if not active_mode:
  278. active_mode = Preferences.getInstance().getValue("cura/active_mode")
  279. except KeyError:
  280. # If there is no preferences file, it's not a workspace, so notify user of failure.
  281. Logger.log("w", "File %s is not a valid workspace.", file_name)
  282. return WorkspaceReader.PreReadResult.failed
  283. # In case we use preRead() to check if a file is a valid project file, we don't want to show a dialog.
  284. if not show_dialog:
  285. return WorkspaceReader.PreReadResult.accepted
  286. # prepare data for the dialog
  287. num_extruders = extruder_definition_container_count
  288. if num_extruders == 0:
  289. num_extruders = 1 # No extruder stacks found, which means there is one extruder
  290. extruders = num_extruders * [""]
  291. # Show the dialog, informing the user what is about to happen.
  292. self._dialog.setMachineConflict(machine_conflict)
  293. self._dialog.setQualityChangesConflict(quality_changes_conflict)
  294. self._dialog.setDefinitionChangesConflict(definition_changes_conflict)
  295. self._dialog.setMaterialConflict(material_conflict)
  296. self._dialog.setHasVisibleSettingsField(has_visible_settings_string)
  297. self._dialog.setNumVisibleSettings(num_visible_settings)
  298. self._dialog.setQualityName(quality_name)
  299. self._dialog.setQualityType(quality_type)
  300. self._dialog.setNumSettingsOverridenByQualityChanges(num_settings_overriden_by_quality_changes)
  301. self._dialog.setNumUserSettings(num_user_settings)
  302. self._dialog.setActiveMode(active_mode)
  303. self._dialog.setMachineName(machine_name)
  304. self._dialog.setMaterialLabels(material_labels)
  305. self._dialog.setMachineType(machine_type)
  306. self._dialog.setExtruders(extruders)
  307. self._dialog.setVariantType(variant_type_name)
  308. self._dialog.setHasObjectsOnPlate(Application.getInstance().platformActivity)
  309. self._dialog.show()
  310. # Block until the dialog is closed.
  311. self._dialog.waitForClose()
  312. if self._dialog.getResult() == {}:
  313. return WorkspaceReader.PreReadResult.cancelled
  314. self._resolve_strategies = self._dialog.getResult()
  315. #
  316. # There can be 3 resolve strategies coming from the dialog:
  317. # - new: create a new container
  318. # - override: override the existing container
  319. # - None: There is no conflict, which means containers with the same IDs may or may not be there already.
  320. # If there is an existing container, there is no conflict between them, and default to "override"
  321. # If there is no existing container, default to "new"
  322. #
  323. # Default values
  324. for key, strategy in self._resolve_strategies.items():
  325. if key not in containers_found_dict or strategy is not None:
  326. continue
  327. self._resolve_strategies[key] = "override" if containers_found_dict[key] else "new"
  328. return WorkspaceReader.PreReadResult.accepted
  329. ## Overrides an ExtruderStack in the given GlobalStack and returns the new ExtruderStack.
  330. def _overrideExtruderStack(self, global_stack, extruder_file_content, extruder_stack_file):
  331. # Get extruder position first
  332. extruder_config = configparser.ConfigParser()
  333. extruder_config.read_string(extruder_file_content)
  334. if not extruder_config.has_option("metadata", "position"):
  335. msg = "Could not find 'metadata/position' in extruder stack file"
  336. Logger.log("e", "Could not find 'metadata/position' in extruder stack file")
  337. raise RuntimeError(msg)
  338. extruder_position = extruder_config.get("metadata", "position")
  339. try:
  340. extruder_stack = global_stack.extruders[extruder_position]
  341. except KeyError:
  342. Logger.log("w", "Could not find the matching extruder stack to override for position %s", extruder_position)
  343. return None
  344. # Override the given extruder stack
  345. extruder_stack.deserialize(extruder_file_content, file_name = extruder_stack_file)
  346. # return the new ExtruderStack
  347. return extruder_stack
  348. ## Read the project file
  349. # Add all the definitions / materials / quality changes that do not exist yet. Then it loads
  350. # all the stacks into the container registry. In some cases it will reuse the container for the global stack.
  351. # It handles old style project files containing .stack.cfg as well as new style project files
  352. # containing global.cfg / extruder.cfg
  353. #
  354. # \param file_name
  355. def read(self, file_name):
  356. archive = zipfile.ZipFile(file_name, "r")
  357. cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")]
  358. # Create a shadow copy of the preferences (we don't want all of the preferences, but we do want to re-use its
  359. # parsing code.
  360. temp_preferences = Preferences()
  361. serialized = archive.open("Cura/preferences.cfg").read().decode("utf-8")
  362. temp_preferences.deserialize(serialized)
  363. # Copy a number of settings from the temp preferences to the global
  364. global_preferences = Preferences.getInstance()
  365. visible_settings = temp_preferences.getValue("general/visible_settings")
  366. if visible_settings is None:
  367. Logger.log("w", "Workspace did not contain visible settings. Leaving visibility unchanged")
  368. else:
  369. global_preferences.setValue("general/visible_settings", visible_settings)
  370. categories_expanded = temp_preferences.getValue("cura/categories_expanded")
  371. if categories_expanded is None:
  372. Logger.log("w", "Workspace did not contain expanded categories. Leaving them unchanged")
  373. else:
  374. global_preferences.setValue("cura/categories_expanded", categories_expanded)
  375. Application.getInstance().expandedCategoriesChanged.emit() # Notify the GUI of the change
  376. self._id_mapping = {}
  377. # We don't add containers right away, but wait right until right before the stack serialization.
  378. # We do this so that if something goes wrong, it's easier to clean up.
  379. containers_to_add = []
  380. global_stack_file, extruder_stack_files = self._determineGlobalAndExtruderStackFiles(file_name, cura_file_names)
  381. global_stack = None
  382. extruder_stacks = []
  383. extruder_stacks_added = []
  384. container_stacks_added = []
  385. containers_added = []
  386. global_stack_id_original = self._stripFileToId(global_stack_file)
  387. global_stack_id_new = global_stack_id_original
  388. global_stack_name_original = self._getMachineNameFromSerializedStack(archive.open(global_stack_file).read().decode("utf-8"))
  389. global_stack_name_new = global_stack_name_original
  390. global_stack_need_rename = False
  391. extruder_stack_id_map = {} # new and old ExtruderStack IDs map
  392. if self._resolve_strategies["machine"] == "new":
  393. # We need a new id if the id already exists
  394. if self._container_registry.findContainerStacks(id = global_stack_id_original):
  395. global_stack_id_new = self.getNewId(global_stack_id_original)
  396. global_stack_need_rename = True
  397. global_stack_name_new = self._container_registry.uniqueName(global_stack_name_original)
  398. for each_extruder_stack_file in extruder_stack_files:
  399. old_container_id = self._stripFileToId(each_extruder_stack_file)
  400. new_container_id = old_container_id
  401. if self._container_registry.findContainerStacks(id = old_container_id):
  402. # get a new name for this extruder
  403. new_container_id = self.getNewId(old_container_id)
  404. extruder_stack_id_map[old_container_id] = new_container_id
  405. # TODO: For the moment we use pretty naive existence checking. If the ID is the same, we assume in quite a few
  406. # TODO: cases that the container loaded is the same (most notable in materials & definitions).
  407. # TODO: It might be possible that we need to add smarter checking in the future.
  408. Logger.log("d", "Workspace loading is checking definitions...")
  409. # Get all the definition files & check if they exist. If not, add them.
  410. definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
  411. for definition_container_file in definition_container_files:
  412. container_id = self._stripFileToId(definition_container_file)
  413. definitions = self._container_registry.findDefinitionContainers(id = container_id)
  414. if not definitions:
  415. definition_container = DefinitionContainer(container_id)
  416. definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"),
  417. file_name = definition_container_file)
  418. self._container_registry.addContainer(definition_container)
  419. Job.yieldThread()
  420. Logger.log("d", "Workspace loading is checking materials...")
  421. material_containers = []
  422. # Get all the material files and check if they exist. If not, add them.
  423. xml_material_profile = self._getXmlProfileClass()
  424. if self._material_container_suffix is None:
  425. self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(xml_material_profile).suffixes[0]
  426. if xml_material_profile:
  427. material_container_files = [name for name in cura_file_names if name.endswith(self._material_container_suffix)]
  428. for material_container_file in material_container_files:
  429. container_id = self._stripFileToId(material_container_file)
  430. materials = self._container_registry.findInstanceContainers(id = container_id)
  431. if not materials:
  432. material_container = xml_material_profile(container_id)
  433. material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"),
  434. file_name = material_container_file)
  435. containers_to_add.append(material_container)
  436. else:
  437. material_container = materials[0]
  438. if not material_container.isReadOnly(): # Only create new materials if they are not read only.
  439. if self._resolve_strategies["material"] == "override":
  440. material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"),
  441. file_name = material_container_file)
  442. elif self._resolve_strategies["material"] == "new":
  443. # Note that we *must* deserialize it with a new ID, as multiple containers will be
  444. # auto created & added.
  445. material_container = xml_material_profile(self.getNewId(container_id))
  446. material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"),
  447. file_name = material_container_file)
  448. containers_to_add.append(material_container)
  449. material_containers.append(material_container)
  450. Job.yieldThread()
  451. Logger.log("d", "Workspace loading is checking instance containers...")
  452. # Get quality_changes and user profiles saved in the workspace
  453. instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
  454. user_instance_containers = []
  455. quality_and_definition_changes_instance_containers = []
  456. for instance_container_file in instance_container_files:
  457. container_id = self._stripFileToId(instance_container_file)
  458. serialized = archive.open(instance_container_file).read().decode("utf-8")
  459. # HACK! we ignore "quality" and "variant" instance containers!
  460. parser = configparser.ConfigParser()
  461. parser.read_string(serialized)
  462. if not parser.has_option("metadata", "type"):
  463. Logger.log("w", "Cannot find metadata/type in %s, ignoring it", instance_container_file)
  464. continue
  465. if parser.get("metadata", "type") in self._ignored_instance_container_types:
  466. continue
  467. instance_container = InstanceContainer(container_id)
  468. # Deserialize InstanceContainer by converting read data from bytes to string
  469. instance_container.deserialize(serialized, file_name = instance_container_file)
  470. container_type = instance_container.getMetaDataEntry("type")
  471. Job.yieldThread()
  472. #
  473. # IMPORTANT:
  474. # If an instance container (or maybe other type of container) exists, and user chooses "Create New",
  475. # we need to rename this container and all references to it, and changing those references are VERY
  476. # HARD.
  477. #
  478. if container_type in self._ignored_instance_container_types:
  479. # Ignore certain instance container types
  480. Logger.log("w", "Ignoring instance container [%s] with type [%s]", container_id, container_type)
  481. continue
  482. elif container_type == "user":
  483. # Check if quality changes already exists.
  484. user_containers = self._container_registry.findInstanceContainers(id = container_id)
  485. if not user_containers:
  486. containers_to_add.append(instance_container)
  487. else:
  488. if self._resolve_strategies["machine"] == "override" or self._resolve_strategies["machine"] is None:
  489. instance_container = user_containers[0]
  490. instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"),
  491. file_name = instance_container_file)
  492. instance_container.setDirty(True)
  493. elif self._resolve_strategies["machine"] == "new":
  494. # The machine is going to get a spiffy new name, so ensure that the id's of user settings match.
  495. old_extruder_id = instance_container.getMetaDataEntry("extruder", None)
  496. if old_extruder_id:
  497. new_extruder_id = extruder_stack_id_map[old_extruder_id]
  498. new_id = new_extruder_id + "_current_settings"
  499. instance_container._id = new_id
  500. instance_container.setName(new_id)
  501. instance_container.setMetaDataEntry("extruder", new_extruder_id)
  502. containers_to_add.append(instance_container)
  503. machine_id = instance_container.getMetaDataEntry("machine", None)
  504. if machine_id:
  505. new_machine_id = self.getNewId(machine_id)
  506. new_id = new_machine_id + "_current_settings"
  507. instance_container._id = new_id
  508. instance_container.setName(new_id)
  509. instance_container.setMetaDataEntry("machine", new_machine_id)
  510. containers_to_add.append(instance_container)
  511. user_instance_containers.append(instance_container)
  512. elif container_type in ("quality_changes", "definition_changes"):
  513. # Check if quality changes already exists.
  514. changes_containers = self._container_registry.findInstanceContainers(id = container_id)
  515. if not changes_containers:
  516. # no existing containers with the same ID, so we can safely add the new one
  517. containers_to_add.append(instance_container)
  518. else:
  519. # we have found existing container with the same ID, so we need to resolve according to the
  520. # selected strategy.
  521. if self._resolve_strategies[container_type] == "override":
  522. instance_container = changes_containers[0]
  523. instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"),
  524. file_name = instance_container_file)
  525. instance_container.setDirty(True)
  526. elif self._resolve_strategies[container_type] == "new":
  527. # TODO: how should we handle the case "new" for quality_changes and definition_changes?
  528. instance_container.setName(self._container_registry.uniqueName(instance_container.getName()))
  529. new_changes_container_id = self.getNewId(instance_container.getId())
  530. instance_container._id = new_changes_container_id
  531. # TODO: we don't know the following is correct or not, need to verify
  532. # AND REFACTOR!!!
  533. if self._resolve_strategies["machine"] == "new":
  534. # The machine is going to get a spiffy new name, so ensure that the id's of user settings match.
  535. old_extruder_id = instance_container.getMetaDataEntry("extruder", None)
  536. # Note that in case of a quality_changes extruder means the definition id of the extruder stack
  537. # For the user settings, it means the actual extruder stack id it's assigned to.
  538. if old_extruder_id and old_extruder_id in extruder_stack_id_map:
  539. new_extruder_id = extruder_stack_id_map[old_extruder_id]
  540. instance_container.setMetaDataEntry("extruder", new_extruder_id)
  541. machine_id = instance_container.getMetaDataEntry("machine", None)
  542. if machine_id:
  543. new_machine_id = self.getNewId(machine_id)
  544. instance_container.setMetaDataEntry("machine", new_machine_id)
  545. containers_to_add.append(instance_container)
  546. elif self._resolve_strategies[container_type] is None:
  547. # The ID already exists, but nothing in the values changed, so do nothing.
  548. pass
  549. quality_and_definition_changes_instance_containers.append(instance_container)
  550. else:
  551. existing_container = self._container_registry.findInstanceContainers(id = container_id)
  552. if not existing_container:
  553. containers_to_add.append(instance_container)
  554. if global_stack_need_rename:
  555. if instance_container.getMetaDataEntry("machine"):
  556. instance_container.setMetaDataEntry("machine", global_stack_id_new)
  557. # Add all the containers right before we try to add / serialize the stack
  558. for container in containers_to_add:
  559. self._container_registry.addContainer(container)
  560. container.setDirty(True)
  561. containers_added.append(container)
  562. # Get the stack(s) saved in the workspace.
  563. Logger.log("d", "Workspace loading is checking stacks containers...")
  564. # load global stack file
  565. try:
  566. stack = None
  567. if self._resolve_strategies["machine"] == "override":
  568. container_stacks = self._container_registry.findContainerStacks(id = global_stack_id_original)
  569. stack = container_stacks[0]
  570. # HACK
  571. # There is a machine, check if it has authentication data. If so, keep that data.
  572. network_authentication_id = container_stacks[0].getMetaDataEntry("network_authentication_id")
  573. network_authentication_key = container_stacks[0].getMetaDataEntry("network_authentication_key")
  574. container_stacks[0].deserialize(archive.open(global_stack_file).read().decode("utf-8"),
  575. file_name = global_stack_file)
  576. if network_authentication_id:
  577. container_stacks[0].addMetaDataEntry("network_authentication_id", network_authentication_id)
  578. if network_authentication_key:
  579. container_stacks[0].addMetaDataEntry("network_authentication_key", network_authentication_key)
  580. elif self._resolve_strategies["machine"] == "new":
  581. # create a new global stack
  582. stack = GlobalStack(global_stack_id_new)
  583. # Deserialize stack by converting read data from bytes to string
  584. stack.deserialize(archive.open(global_stack_file).read().decode("utf-8"),
  585. file_name = global_stack_file)
  586. # Ensure a unique ID and name
  587. stack._id = global_stack_id_new
  588. # Extruder stacks are "bound" to a machine. If we add the machine as a new one, the id of the
  589. # bound machine also needs to change.
  590. if stack.getMetaDataEntry("machine", None):
  591. stack.setMetaDataEntry("machine", global_stack_id_new)
  592. # Only machines need a new name, stacks may be non-unique
  593. stack.setName(global_stack_name_new)
  594. container_stacks_added.append(stack)
  595. self._container_registry.addContainer(stack)
  596. containers_added.append(stack)
  597. else:
  598. Logger.log("e", "Resolve strategy of %s for machine is not supported", self._resolve_strategies["machine"])
  599. # Create a new definition_changes container if it was empty
  600. if stack.definitionChanges == self._container_registry.getEmptyInstanceContainer():
  601. stack.setDefinitionChanges(CuraStackBuilder.createDefinitionChangesContainer(stack, stack.getId() + "_settings"))
  602. global_stack = stack
  603. Job.yieldThread()
  604. except:
  605. Logger.logException("w", "We failed to serialize the stack. Trying to clean up.")
  606. # Something went really wrong. Try to remove any data that we added.
  607. for container in containers_added:
  608. self._container_registry.removeContainer(container.getId())
  609. return
  610. # load extruder stack files
  611. try:
  612. for extruder_stack_file in extruder_stack_files:
  613. container_id = self._stripFileToId(extruder_stack_file)
  614. extruder_file_content = archive.open(extruder_stack_file, "r").read().decode("utf-8")
  615. if self._resolve_strategies["machine"] == "override":
  616. if global_stack.getProperty("machine_extruder_count", "value") > 1:
  617. # deserialize new extruder stack over the current ones (if any)
  618. stack = self._overrideExtruderStack(global_stack, extruder_file_content, extruder_stack_file)
  619. if stack is None:
  620. continue
  621. elif self._resolve_strategies["machine"] == "new":
  622. new_id = extruder_stack_id_map[container_id]
  623. stack = ExtruderStack(new_id)
  624. # HACK: the global stack can have a new name, so we need to make sure that this extruder stack
  625. # references to the new name instead of the old one. Normally, this can be done after
  626. # deserialize() by setting the metadata, but in the case of ExtruderStack, deserialize()
  627. # also does addExtruder() to its machine stack, so we have to make sure that it's pointing
  628. # to the right machine BEFORE deserialization.
  629. extruder_config = configparser.ConfigParser()
  630. extruder_config.read_string(extruder_file_content)
  631. extruder_config.set("metadata", "machine", global_stack_id_new)
  632. tmp_string_io = io.StringIO()
  633. extruder_config.write(tmp_string_io)
  634. extruder_file_content = tmp_string_io.getvalue()
  635. stack.deserialize(extruder_file_content, file_name = extruder_stack_file)
  636. # Ensure a unique ID and name
  637. stack._id = new_id
  638. self._container_registry.addContainer(stack)
  639. extruder_stacks_added.append(stack)
  640. containers_added.append(stack)
  641. else:
  642. Logger.log("w", "Unknown resolve strategy: %s", self._resolve_strategies["machine"])
  643. # Create a new definition_changes container if it was empty
  644. if stack.definitionChanges == self._container_registry.getEmptyInstanceContainer():
  645. stack.setDefinitionChanges(CuraStackBuilder.createDefinitionChangesContainer(stack, stack.getId() + "_settings"))
  646. if stack.getMetaDataEntry("type") == "extruder_train":
  647. extruder_stacks.append(stack)
  648. # If not extruder stacks were saved in the project file (pre 3.1) create one manually
  649. # We re-use the container registry's addExtruderStackForSingleExtrusionMachine method for this
  650. if not extruder_stacks:
  651. stack = self._container_registry.addExtruderStackForSingleExtrusionMachine(global_stack, "fdmextruder")
  652. if stack:
  653. extruder_stacks.append(stack)
  654. except:
  655. Logger.logException("w", "We failed to serialize the stack. Trying to clean up.")
  656. # Something went really wrong. Try to remove any data that we added.
  657. for container in containers_added:
  658. self._container_registry.removeContainer(container.getId())
  659. return
  660. # Check quality profiles to make sure that if one stack has the "not supported" quality profile,
  661. # all others should have the same.
  662. #
  663. # This block code tries to fix the following problems in Cura 3.0 and earlier:
  664. # 1. The upgrade script can rename all "Not Supported" quality profiles to "empty_quality", but it cannot fix
  665. # the problem that the global stack the extruder stacks may have different quality profiles. The code
  666. # below loops over all stacks and make sure that if there is one stack with "Not Supported" profile, the
  667. # rest should also use the "Not Supported" profile.
  668. # 2. In earlier versions (at least 2.7 and 3.0), a wrong quality profile could be assigned to a stack. For
  669. # example, a UM3 can have a BB 0.8 variant with "aa04_pla_fast" quality profile enabled. To fix this,
  670. # in the code below we also check the actual available quality profiles for the machine.
  671. #
  672. has_not_supported = False
  673. for stack in [global_stack] + extruder_stacks:
  674. if stack.quality.getId() == "empty_quality":
  675. has_not_supported = True
  676. break
  677. if not has_not_supported:
  678. available_quality = QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_stack, extruder_stacks)
  679. has_not_supported = not available_quality
  680. if has_not_supported:
  681. empty_quality_container = self._container_registry.findInstanceContainers(id = "empty_quality")[0]
  682. for stack in [global_stack] + extruder_stacks:
  683. stack.replaceContainer(_ContainerIndexes.Quality, empty_quality_container)
  684. # Fix quality:
  685. # The quality specified in an old project file can be wrong, for example, for UM2, it should be "um2_normal"
  686. # but instead it was "normal". This should be fixed by setting it to the correct quality.
  687. # Note that this only seems to happen on single-extrusion machines on the global stack, so we only apply the
  688. # fix for that
  689. quality = global_stack.quality
  690. if quality.getId() not in ("empty", "empty_quality"):
  691. quality_type = quality.getMetaDataEntry("quality_type")
  692. quality_containers = self._container_registry.findInstanceContainers(definition = global_stack.definition.getId(),
  693. type = "quality",
  694. quality_type = quality_type)
  695. quality_containers = [q for q in quality_containers if q.getMetaDataEntry("material", "") == ""]
  696. if quality_containers:
  697. global_stack.quality = quality_containers[0]
  698. else:
  699. # look for "fdmprinter" qualities if the machine-specific qualities cannot be found
  700. quality_containers = self._container_registry.findInstanceContainers(definition = "fdmprinter",
  701. type = "quality",
  702. quality_type = quality_type)
  703. quality_containers = [q for q in quality_containers if q.getMetaDataEntry("material", "") == ""]
  704. if quality_containers:
  705. global_stack.quality = quality_containers[0]
  706. else:
  707. # the quality_type of the quality profile cannot be found.
  708. # this can happen if a quality_type has been removed in a newer version, for example:
  709. # "extra_coarse" is removed from 2.7 to 3.0
  710. # in this case, the quality will be reset to "normal"
  711. quality_containers = self._container_registry.findInstanceContainers(
  712. definition = global_stack.definition.getId(),
  713. type = "quality",
  714. quality_type = "normal")
  715. quality_containers = [q for q in quality_containers if q.getMetaDataEntry("material", "") == ""]
  716. if quality_containers:
  717. global_stack.quality = quality_containers[0]
  718. else:
  719. # This should not happen!
  720. Logger.log("e", "Cannot find quality normal for global stack [%s] [%s]",
  721. global_stack.getId(), global_stack.definition.getId())
  722. global_stack.quality = self._container_registry.findInstanceContainers(id = "empty_quality")[0]
  723. # Replacing the old containers if resolve is "new".
  724. # When resolve is "new", some containers will get renamed, so all the other containers that reference to those
  725. # MUST get updated too.
  726. #
  727. if self._resolve_strategies["machine"] == "new":
  728. # A new machine was made, but it was serialized with the wrong user container. Fix that now.
  729. for container in user_instance_containers:
  730. # replacing the container ID for user instance containers for the extruders
  731. extruder_id = container.getMetaDataEntry("extruder", None)
  732. if extruder_id:
  733. for extruder in extruder_stacks:
  734. if extruder.getId() == extruder_id:
  735. extruder.userChanges = container
  736. continue
  737. # replacing the container ID for user instance containers for the machine
  738. machine_id = container.getMetaDataEntry("machine", None)
  739. if machine_id:
  740. if global_stack.getId() == machine_id:
  741. global_stack.userChanges = container
  742. continue
  743. for changes_container_type in ("quality_changes", "definition_changes"):
  744. if self._resolve_strategies[changes_container_type] == "new":
  745. # Quality changes needs to get a new ID, added to registry and to the right stacks
  746. for each_changes_container in quality_and_definition_changes_instance_containers:
  747. # NOTE: The renaming and giving new IDs are possibly redundant because they are done in the
  748. # instance container loading part.
  749. new_id = each_changes_container.getId()
  750. # Find the old (current) changes container in the global stack
  751. if changes_container_type == "quality_changes":
  752. old_container = global_stack.qualityChanges
  753. elif changes_container_type == "definition_changes":
  754. old_container = global_stack.definitionChanges
  755. # sanity checks
  756. # NOTE: The following cases SHOULD NOT happen!!!!
  757. if not old_container:
  758. Logger.log("e", "We try to get [%s] from the global stack [%s] but we got None instead!",
  759. changes_container_type, global_stack.getId())
  760. # Replace the quality/definition changes container if it's in the GlobalStack
  761. # NOTE: we can get an empty container here, but the IDs will not match,
  762. # so this comparison is fine.
  763. if self._id_mapping.get(old_container.getId()) == new_id:
  764. if changes_container_type == "quality_changes":
  765. global_stack.qualityChanges = each_changes_container
  766. elif changes_container_type == "definition_changes":
  767. global_stack.definitionChanges = each_changes_container
  768. continue
  769. # Replace the quality/definition changes container if it's in one of the ExtruderStacks
  770. for each_extruder_stack in extruder_stacks:
  771. changes_container = None
  772. if changes_container_type == "quality_changes":
  773. changes_container = each_extruder_stack.qualityChanges
  774. elif changes_container_type == "definition_changes":
  775. changes_container = each_extruder_stack.definitionChanges
  776. # sanity checks
  777. # NOTE: The following cases SHOULD NOT happen!!!!
  778. if not changes_container:
  779. Logger.log("e", "We try to get [%s] from the extruder stack [%s] but we got None instead!",
  780. changes_container_type, each_extruder_stack.getId())
  781. # NOTE: we can get an empty container here, but the IDs will not match,
  782. # so this comparison is fine.
  783. if self._id_mapping.get(changes_container.getId()) == new_id:
  784. if changes_container_type == "quality_changes":
  785. each_extruder_stack.qualityChanges = each_changes_container
  786. elif changes_container_type == "definition_changes":
  787. each_extruder_stack.definitionChanges = each_changes_container
  788. if self._resolve_strategies["material"] == "new":
  789. # the actual material instance container can have an ID such as
  790. # <material>_<machine>_<variant>
  791. # which cannot be determined immediately, so here we use a HACK to find the right new material
  792. # instance ID:
  793. # - get the old material IDs for all material
  794. # - find the old material with the longest common prefix in ID, that's the old material
  795. # - update the name by replacing the old prefix with the new
  796. # - find the new material container and set it to the stack
  797. old_to_new_material_dict = {}
  798. for each_material in material_containers:
  799. # find the material's old name
  800. for old_id, new_id in self._id_mapping.items():
  801. if each_material.getId() == new_id:
  802. old_to_new_material_dict[old_id] = each_material
  803. break
  804. # replace old material in global and extruder stacks with new
  805. self._replaceStackMaterialWithNew(global_stack, old_to_new_material_dict)
  806. if extruder_stacks:
  807. for each_extruder_stack in extruder_stacks:
  808. self._replaceStackMaterialWithNew(each_extruder_stack, old_to_new_material_dict)
  809. if extruder_stacks:
  810. for stack in extruder_stacks:
  811. ExtruderManager.getInstance().registerExtruder(stack, global_stack.getId())
  812. Logger.log("d", "Workspace loading is notifying rest of the code of changes...")
  813. if self._resolve_strategies["machine"] == "new":
  814. for stack in extruder_stacks:
  815. stack.setNextStack(global_stack)
  816. stack.containersChanged.emit(stack.getTop())
  817. # Actually change the active machine.
  818. Application.getInstance().setGlobalContainerStack(global_stack)
  819. # Notify everything/one that is to notify about changes.
  820. global_stack.containersChanged.emit(global_stack.getTop())
  821. # Load all the nodes / meshdata of the workspace
  822. nodes = self._3mf_mesh_reader.read(file_name)
  823. if nodes is None:
  824. nodes = []
  825. base_file_name = os.path.basename(file_name)
  826. if base_file_name.endswith(".curaproject.3mf"):
  827. base_file_name = base_file_name[:base_file_name.rfind(".curaproject.3mf")]
  828. self.setWorkspaceName(base_file_name)
  829. return nodes
  830. ## HACK: Replaces the material container in the given stack with a newly created material container.
  831. # This function is used when the user chooses to resolve material conflicts by creating new ones.
  832. def _replaceStackMaterialWithNew(self, stack, old_new_material_dict):
  833. # The material containers in the project file are 'parent' material such as "generic_pla",
  834. # but a material container used in a global/extruder stack is a 'child' material,
  835. # such as "generic_pla_ultimaker3_AA_0.4", which can be formalised as the following:
  836. #
  837. # <material_name>_<machine_name>_<variant_name>
  838. #
  839. # In the project loading, when a user chooses to resolve material conflicts by creating new ones,
  840. # the old 'parent' material ID and the new 'parent' material ID are known, but not the child material IDs.
  841. # In this case, the global stack and the extruder stacks need to use the newly created material, but the
  842. # material containers they use are 'child' material. So, here, we need to find the right 'child' material for
  843. # the stacks.
  844. #
  845. # This hack approach works as follows:
  846. # - No matter there is a child material or not, the actual material we are looking for has the prefix
  847. # "<material_name>", which is the old material name. For the material in a stack, we know that the new
  848. # material's ID will be "<new_material_name>_blabla..", so we just need to replace the old material ID
  849. # with the new one to get the new 'child' material.
  850. # - Because the material containers have IDs such as "m #nn", if we use simple prefix matching, there can
  851. # be a problem in the following scenario:
  852. # - there are two materials in the project file, namely "m #1" and "m #11"
  853. # - the child materials in use are for example: "m #1_um3_aa04", "m #11_um3_aa04"
  854. # - if we only check for a simple prefix match, then "m #11_um3_aa04" will match with "m #1", but they
  855. # are not the same material
  856. # To avoid this, when doing the prefix matching, we use the result with the longest mactching prefix.
  857. # find the old material ID
  858. old_material_id_in_stack = stack.material.getId()
  859. best_matching_old_material_id = None
  860. best_matching_old_meterial_prefix_length = -1
  861. for old_parent_material_id in old_new_material_dict:
  862. if len(old_parent_material_id) < best_matching_old_meterial_prefix_length:
  863. continue
  864. if len(old_parent_material_id) <= len(old_material_id_in_stack):
  865. if old_parent_material_id == old_material_id_in_stack[0:len(old_parent_material_id)]:
  866. best_matching_old_meterial_prefix_length = len(old_parent_material_id)
  867. best_matching_old_material_id = old_parent_material_id
  868. if best_matching_old_material_id is None:
  869. Logger.log("w", "Cannot find any matching old material ID for stack [%s] material [%s]. Something can go wrong",
  870. stack.getId(), old_material_id_in_stack)
  871. return
  872. # find the new material container
  873. new_material_id = old_new_material_dict[best_matching_old_material_id].getId() + old_material_id_in_stack[len(best_matching_old_material_id):]
  874. new_material_containers = self._container_registry.findInstanceContainers(id = new_material_id, type = "material")
  875. if not new_material_containers:
  876. Logger.log("e", "Cannot find new material container [%s]", new_material_id)
  877. return
  878. # replace the material in the given stack
  879. stack.material = new_material_containers[0]
  880. def _stripFileToId(self, file):
  881. mime_type = MimeTypeDatabase.getMimeTypeForFile(file)
  882. file = mime_type.stripExtension(file)
  883. return file.replace("Cura/", "")
  884. def _getXmlProfileClass(self):
  885. return self._container_registry.getContainerForMimeType(MimeTypeDatabase.getMimeType("application/x-ultimaker-material-profile"))
  886. ## Get the list of ID's of all containers in a container stack by partially parsing it's serialized data.
  887. def _getContainerIdListFromSerialized(self, serialized):
  888. parser = configparser.ConfigParser(interpolation=None, empty_lines_in_values=False)
  889. parser.read_string(serialized)
  890. container_ids = []
  891. if "containers" in parser:
  892. for index, container_id in parser.items("containers"):
  893. container_ids.append(container_id)
  894. elif parser.has_option("general", "containers"):
  895. container_string = parser["general"].get("containers", "")
  896. container_list = container_string.split(",")
  897. container_ids = [container_id for container_id in container_list if container_id != ""]
  898. # HACK: there used to be 6 containers numbering from 0 to 5 in a stack,
  899. # now we have 7: index 5 becomes "definition_changes"
  900. if len(container_ids) == 6:
  901. # Hack; We used to not save the definition changes. Fix this.
  902. container_ids.insert(5, "empty")
  903. return container_ids
  904. def _getMachineNameFromSerializedStack(self, serialized):
  905. parser = configparser.ConfigParser(interpolation=None, empty_lines_in_values=False)
  906. parser.read_string(serialized)
  907. return parser["general"].get("name", "")
  908. def _getMaterialLabelFromSerialized(self, serialized):
  909. data = ET.fromstring(serialized)
  910. metadata = data.iterfind("./um:metadata/um:name/um:label", {"um": "http://www.ultimaker.com/material"})
  911. for entry in metadata:
  912. return entry.text