ThreeMFWorkspaceReader.py 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681
  1. from UM.Workspace.WorkspaceReader import WorkspaceReader
  2. from UM.Application import Application
  3. from UM.Logger import Logger
  4. from UM.i18n import i18nCatalog
  5. from UM.Settings.ContainerStack import ContainerStack
  6. from UM.Settings.DefinitionContainer import DefinitionContainer
  7. from UM.Settings.InstanceContainer import InstanceContainer
  8. from UM.Settings.ContainerRegistry import ContainerRegistry
  9. from UM.MimeTypeDatabase import MimeTypeDatabase
  10. from UM.Job import Job
  11. from UM.Preferences import Preferences
  12. from .WorkspaceDialog import WorkspaceDialog
  13. import xml.etree.ElementTree as ET
  14. from cura.Settings.ExtruderManager import ExtruderManager
  15. from cura.Settings.ExtruderStack import ExtruderStack
  16. from cura.Settings.GlobalStack import GlobalStack
  17. from configparser import ConfigParser
  18. import zipfile
  19. import io
  20. import configparser
  21. i18n_catalog = i18nCatalog("cura")
  22. ## Base implementation for reading 3MF workspace files.
  23. class ThreeMFWorkspaceReader(WorkspaceReader):
  24. def __init__(self):
  25. super().__init__()
  26. self._supported_extensions = [".3mf"]
  27. self._dialog = WorkspaceDialog()
  28. self._3mf_mesh_reader = None
  29. self._container_registry = ContainerRegistry.getInstance()
  30. # suffixes registered with the MineTypes don't start with a dot '.'
  31. self._definition_container_suffix = "." + ContainerRegistry.getMimeTypeForContainer(DefinitionContainer).preferredSuffix
  32. self._material_container_suffix = None # We have to wait until all other plugins are loaded before we can set it
  33. self._instance_container_suffix = "." + ContainerRegistry.getMimeTypeForContainer(InstanceContainer).preferredSuffix
  34. self._container_stack_suffix = "." + ContainerRegistry.getMimeTypeForContainer(ContainerStack).preferredSuffix
  35. self._extruder_stack_suffix = "." + ContainerRegistry.getMimeTypeForContainer(ExtruderStack).preferredSuffix
  36. self._global_stack_suffix = "." + ContainerRegistry.getMimeTypeForContainer(GlobalStack).preferredSuffix
  37. self._resolve_strategies = {}
  38. self._id_mapping = {}
  39. ## Get a unique name based on the old_id. This is different from directly calling the registry in that it caches results.
  40. # This has nothing to do with speed, but with getting consistent new naming for instances & objects.
  41. def getNewId(self, old_id):
  42. if old_id not in self._id_mapping:
  43. self._id_mapping[old_id] = self._container_registry.uniqueName(old_id)
  44. return self._id_mapping[old_id]
  45. ## Separates the given file list into a list of GlobalStack files and a list of ExtruderStack files.
  46. #
  47. # In old versions, extruder stack files have the same suffix as container stack files ".stack.cfg".
  48. #
  49. def _determineGlobalAndExtruderStackFiles(self, project_file_name, file_list):
  50. archive = zipfile.ZipFile(project_file_name, "r")
  51. global_stack_file_list = [name for name in file_list if name.endswith(self._global_stack_suffix)]
  52. extruder_stack_file_list = [name for name in file_list if name.endswith(self._extruder_stack_suffix)]
  53. # separate container stack files and extruder stack files
  54. files_to_determine = [name for name in file_list if name.endswith(self._container_stack_suffix)]
  55. for file_name in files_to_determine:
  56. # FIXME: HACK!
  57. # We need to know the type of the stack file, but we can only know it if we deserialize it.
  58. # The default ContainerStack.deserialize() will connect signals, which is not desired in this case.
  59. # Since we know that the stack files are INI files, so we directly use the ConfigParser to parse them.
  60. serialized = archive.open(file_name).read().decode("utf-8")
  61. stack_config = ConfigParser()
  62. stack_config.read_string(serialized)
  63. # sanity check
  64. if not stack_config.has_option("metadata", "type"):
  65. Logger.log("e", "%s in %s doesn't seem to be valid stack file", file_name, project_file_name)
  66. continue
  67. stack_type = stack_config.get("metadata", "type")
  68. if stack_type == "extruder_train":
  69. extruder_stack_file_list.append(file_name)
  70. elif stack_type == "machine":
  71. global_stack_file_list.append(file_name)
  72. else:
  73. Logger.log("w", "Unknown container stack type '%s' from %s in %s",
  74. stack_type, file_name, project_file_name)
  75. if len(global_stack_file_list) != 1:
  76. raise RuntimeError("More than one global stack file found: [%s]" % str(global_stack_file_list))
  77. return global_stack_file_list[0], extruder_stack_file_list
  78. ## read some info so we can make decisions
  79. # \param file_name
  80. # \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.
  81. def preRead(self, file_name, show_dialog=True, *args, **kwargs):
  82. self._3mf_mesh_reader = Application.getInstance().getMeshFileHandler().getReaderForFile(file_name)
  83. if self._3mf_mesh_reader and self._3mf_mesh_reader.preRead(file_name) == WorkspaceReader.PreReadResult.accepted:
  84. pass
  85. else:
  86. Logger.log("w", "Could not find reader that was able to read the scene data for 3MF workspace")
  87. return WorkspaceReader.PreReadResult.failed
  88. machine_name = ""
  89. machine_type = ""
  90. variant_type_name = i18n_catalog.i18nc("@label", "Nozzle")
  91. num_extruders = 0
  92. # Check if there are any conflicts, so we can ask the user.
  93. archive = zipfile.ZipFile(file_name, "r")
  94. cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")]
  95. # A few lists of containers in this project files.
  96. # When loading the global stack file, it may be associated with those containers, which may or may not be
  97. # in Cura already, so we need to provide them as alternative search lists.
  98. definition_container_list = []
  99. instance_container_list = []
  100. material_container_list = []
  101. definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
  102. for definition_container_file in definition_container_files:
  103. container_id = self._stripFileToId(definition_container_file)
  104. definitions = self._container_registry.findDefinitionContainers(id=container_id)
  105. if not definitions:
  106. definition_container = DefinitionContainer(container_id)
  107. definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"))
  108. else:
  109. definition_container = definitions[0]
  110. definition_container_list.append(definition_container)
  111. if definition_container.getMetaDataEntry("type") != "extruder":
  112. machine_type = definition_container.getName()
  113. variant_type_name = definition_container.getMetaDataEntry("variants_name", variant_type_name)
  114. else:
  115. num_extruders += 1
  116. Job.yieldThread()
  117. if num_extruders == 0:
  118. num_extruders = 1 # No extruder stacks found, which means there is one extruder
  119. extruders = num_extruders * [""]
  120. material_labels = []
  121. material_conflict = False
  122. xml_material_profile = self._getXmlProfileClass()
  123. if self._material_container_suffix is None:
  124. self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(xml_material_profile).preferredSuffix
  125. if xml_material_profile:
  126. material_container_files = [name for name in cura_file_names if name.endswith(self._material_container_suffix)]
  127. for material_container_file in material_container_files:
  128. container_id = self._stripFileToId(material_container_file)
  129. materials = self._container_registry.findInstanceContainers(id=container_id)
  130. material_labels.append(self._getMaterialLabelFromSerialized(archive.open(material_container_file).read().decode("utf-8")))
  131. if materials and not materials[0].isReadOnly(): # Only non readonly materials can be in conflict
  132. material_conflict = True
  133. Job.yieldThread()
  134. # Check if any quality_changes instance container is in conflict.
  135. instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
  136. quality_name = ""
  137. quality_type = ""
  138. num_settings_overriden_by_quality_changes = 0 # How many settings are changed by the quality changes
  139. num_user_settings = 0
  140. for instance_container_file in instance_container_files:
  141. container_id = self._stripFileToId(instance_container_file)
  142. instance_container = InstanceContainer(container_id)
  143. # Deserialize InstanceContainer by converting read data from bytes to string
  144. instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"))
  145. instance_container_list.append(instance_container)
  146. container_type = instance_container.getMetaDataEntry("type")
  147. if container_type == "quality_changes":
  148. quality_name = instance_container.getName()
  149. num_settings_overriden_by_quality_changes += len(instance_container._instances)
  150. # Check if quality changes already exists.
  151. quality_changes = self._container_registry.findInstanceContainers(id = container_id)
  152. if quality_changes:
  153. # Check if there really is a conflict by comparing the values
  154. if quality_changes[0] != instance_container:
  155. quality_changes_conflict = True
  156. elif container_type == "quality":
  157. # If the quality name is not set (either by quality or changes, set it now)
  158. # Quality changes should always override this (as they are "on top")
  159. if quality_name == "":
  160. quality_name = instance_container.getName()
  161. quality_type = instance_container.getName()
  162. elif container_type == "user":
  163. num_user_settings += len(instance_container._instances)
  164. Job.yieldThread()
  165. # Load ContainerStack files and ExtruderStack files
  166. global_stack_file, extruder_stack_files = self._determineGlobalAndExtruderStackFiles(
  167. file_name, cura_file_names)
  168. self._resolve_strategies = {"machine": None, "quality_changes": None, "material": None}
  169. machine_conflict = False
  170. quality_changes_conflict = False
  171. for container_stack_file in [global_stack_file] + extruder_stack_files:
  172. container_id = self._stripFileToId(container_stack_file)
  173. serialized = archive.open(container_stack_file).read().decode("utf-8")
  174. if machine_name == "":
  175. machine_name = self._getMachineNameFromSerializedStack(serialized)
  176. stacks = self._container_registry.findContainerStacks(id = container_id)
  177. if stacks:
  178. # Check if there are any changes at all in any of the container stacks.
  179. id_list = self._getContainerIdListFromSerialized(serialized)
  180. for index, container_id in enumerate(id_list):
  181. if stacks[0].getContainer(index).getId() != container_id:
  182. machine_conflict = True
  183. Job.yieldThread()
  184. num_visible_settings = 0
  185. try:
  186. temp_preferences = Preferences()
  187. temp_preferences.readFromFile(io.TextIOWrapper(archive.open("Cura/preferences.cfg"))) # We need to wrap it, else the archive parser breaks.
  188. visible_settings_string = temp_preferences.getValue("general/visible_settings")
  189. if visible_settings_string is not None:
  190. num_visible_settings = len(visible_settings_string.split(";"))
  191. active_mode = temp_preferences.getValue("cura/active_mode")
  192. if not active_mode:
  193. active_mode = Preferences.getInstance().getValue("cura/active_mode")
  194. except KeyError:
  195. # If there is no preferences file, it's not a workspace, so notify user of failure.
  196. Logger.log("w", "File %s is not a valid workspace.", file_name)
  197. return WorkspaceReader.PreReadResult.failed
  198. # In case we use preRead() to check if a file is a valid project file, we don't want to show a dialog.
  199. if not show_dialog:
  200. return WorkspaceReader.PreReadResult.accepted
  201. # Show the dialog, informing the user what is about to happen.
  202. self._dialog.setMachineConflict(machine_conflict)
  203. self._dialog.setQualityChangesConflict(quality_changes_conflict)
  204. self._dialog.setMaterialConflict(material_conflict)
  205. self._dialog.setNumVisibleSettings(num_visible_settings)
  206. self._dialog.setQualityName(quality_name)
  207. self._dialog.setQualityType(quality_type)
  208. self._dialog.setNumSettingsOverridenByQualityChanges(num_settings_overriden_by_quality_changes)
  209. self._dialog.setNumUserSettings(num_user_settings)
  210. self._dialog.setActiveMode(active_mode)
  211. self._dialog.setMachineName(machine_name)
  212. self._dialog.setMaterialLabels(material_labels)
  213. self._dialog.setMachineType(machine_type)
  214. self._dialog.setExtruders(extruders)
  215. self._dialog.setVariantType(variant_type_name)
  216. self._dialog.setHasObjectsOnPlate(Application.getInstance().platformActivity)
  217. self._dialog.show()
  218. # Block until the dialog is closed.
  219. self._dialog.waitForClose()
  220. if self._dialog.getResult() == {}:
  221. return WorkspaceReader.PreReadResult.cancelled
  222. self._resolve_strategies = self._dialog.getResult()
  223. # Default values
  224. for k, v in self._resolve_strategies.items():
  225. if v is None:
  226. self._resolve_strategies[k] = "new"
  227. return WorkspaceReader.PreReadResult.accepted
  228. ## Read the project file
  229. # Add all the definitions / materials / quality changes that do not exist yet. Then it loads
  230. # all the stacks into the container registry. In some cases it will reuse the container for the global stack.
  231. # It handles old style project files containing .stack.cfg as well as new style project files
  232. # containing global.cfg / extruder.cfg
  233. #
  234. # \param file_name
  235. def read(self, file_name):
  236. archive = zipfile.ZipFile(file_name, "r")
  237. cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")]
  238. # Create a shadow copy of the preferences (we don't want all of the preferences, but we do want to re-use its
  239. # parsing code.
  240. temp_preferences = Preferences()
  241. temp_preferences.readFromFile(io.TextIOWrapper(archive.open("Cura/preferences.cfg"))) # We need to wrap it, else the archive parser breaks.
  242. # Copy a number of settings from the temp preferences to the global
  243. global_preferences = Preferences.getInstance()
  244. visible_settings = temp_preferences.getValue("general/visible_settings")
  245. if visible_settings is None:
  246. Logger.log("w", "Workspace did not contain visible settings. Leaving visibility unchanged")
  247. else:
  248. global_preferences.setValue("general/visible_settings", visible_settings)
  249. categories_expanded = temp_preferences.getValue("cura/categories_expanded")
  250. if categories_expanded is None:
  251. Logger.log("w", "Workspace did not contain expanded categories. Leaving them unchanged")
  252. else:
  253. global_preferences.setValue("cura/categories_expanded", categories_expanded)
  254. Application.getInstance().expandedCategoriesChanged.emit() # Notify the GUI of the change
  255. self._id_mapping = {}
  256. # We don't add containers right away, but wait right until right before the stack serialization.
  257. # We do this so that if something goes wrong, it's easier to clean up.
  258. containers_to_add = []
  259. global_stack_file, extruder_stack_files = self._determineGlobalAndExtruderStackFiles(file_name, cura_file_names)
  260. global_stack = None
  261. extruder_stacks = []
  262. extruder_stacks_added = []
  263. container_stacks_added = []
  264. global_stack_id_original = self._stripFileToId(global_stack_file)
  265. global_stack_id_new = global_stack_id_original
  266. global_stack_need_rename = False
  267. if self._resolve_strategies["machine"] == "new":
  268. # We need a new id if the id already exists
  269. if self._container_registry.findContainerStacks(id = global_stack_id_original):
  270. global_stack_id_new = self.getNewId(global_stack_id_original)
  271. global_stack_need_rename = True
  272. # TODO: For the moment we use pretty naive existence checking. If the ID is the same, we assume in quite a few
  273. # TODO: cases that the container loaded is the same (most notable in materials & definitions).
  274. # TODO: It might be possible that we need to add smarter checking in the future.
  275. Logger.log("d", "Workspace loading is checking definitions...")
  276. # Get all the definition files & check if they exist. If not, add them.
  277. definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
  278. for definition_container_file in definition_container_files:
  279. container_id = self._stripFileToId(definition_container_file)
  280. definitions = self._container_registry.findDefinitionContainers(id = container_id)
  281. if not definitions:
  282. definition_container = DefinitionContainer(container_id)
  283. definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"))
  284. self._container_registry.addContainer(definition_container)
  285. Job.yieldThread()
  286. Logger.log("d", "Workspace loading is checking materials...")
  287. material_containers = []
  288. # Get all the material files and check if they exist. If not, add them.
  289. xml_material_profile = self._getXmlProfileClass()
  290. if self._material_container_suffix is None:
  291. self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(xml_material_profile).suffixes[0]
  292. if xml_material_profile:
  293. material_container_files = [name for name in cura_file_names if name.endswith(self._material_container_suffix)]
  294. for material_container_file in material_container_files:
  295. container_id = self._stripFileToId(material_container_file)
  296. materials = self._container_registry.findInstanceContainers(id = container_id)
  297. if not materials:
  298. material_container = xml_material_profile(container_id)
  299. material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"))
  300. containers_to_add.append(material_container)
  301. else:
  302. if not materials[0].isReadOnly(): # Only create new materials if they are not read only.
  303. if self._resolve_strategies["material"] == "override":
  304. materials[0].deserialize(archive.open(material_container_file).read().decode("utf-8"))
  305. elif self._resolve_strategies["material"] == "new":
  306. # Note that we *must* deserialize it with a new ID, as multiple containers will be
  307. # auto created & added.
  308. material_container = xml_material_profile(self.getNewId(container_id))
  309. material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"))
  310. containers_to_add.append(material_container)
  311. material_containers.append(material_container)
  312. Job.yieldThread()
  313. Logger.log("d", "Workspace loading is checking instance containers...")
  314. # Get quality_changes and user profiles saved in the workspace
  315. instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
  316. user_instance_containers = []
  317. quality_and_definition_changes_instance_containers = []
  318. for instance_container_file in instance_container_files:
  319. container_id = self._stripFileToId(instance_container_file)
  320. instance_container = InstanceContainer(container_id)
  321. # Deserialize InstanceContainer by converting read data from bytes to string
  322. instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"))
  323. container_type = instance_container.getMetaDataEntry("type")
  324. Job.yieldThread()
  325. if container_type == "user":
  326. # Check if quality changes already exists.
  327. user_containers = self._container_registry.findInstanceContainers(id = container_id)
  328. if not user_containers:
  329. containers_to_add.append(instance_container)
  330. else:
  331. if self._resolve_strategies["machine"] == "override" or self._resolve_strategies["machine"] is None:
  332. user_containers[0].deserialize(archive.open(instance_container_file).read().decode("utf-8"))
  333. elif self._resolve_strategies["machine"] == "new":
  334. # The machine is going to get a spiffy new name, so ensure that the id's of user settings match.
  335. extruder_id = instance_container.getMetaDataEntry("extruder", None)
  336. if extruder_id:
  337. new_id = self.getNewId(extruder_id) + "_current_settings"
  338. instance_container._id = new_id
  339. instance_container.setName(new_id)
  340. instance_container.setMetaDataEntry("extruder", self.getNewId(extruder_id))
  341. containers_to_add.append(instance_container)
  342. machine_id = instance_container.getMetaDataEntry("machine", None)
  343. if machine_id:
  344. new_id = self.getNewId(machine_id) + "_current_settings"
  345. instance_container._id = new_id
  346. instance_container.setName(new_id)
  347. instance_container.setMetaDataEntry("machine", self.getNewId(machine_id))
  348. containers_to_add.append(instance_container)
  349. user_instance_containers.append(instance_container)
  350. elif container_type in ("quality_changes", "definition_changes"):
  351. # Check if quality changes already exists.
  352. changes_containers = self._container_registry.findInstanceContainers(id = container_id)
  353. if not changes_containers:
  354. containers_to_add.append(instance_container)
  355. else:
  356. if self._resolve_strategies[container_type] == "override":
  357. changes_containers[0].deserialize(archive.open(instance_container_file).read().decode("utf-8"))
  358. elif self._resolve_strategies[container_type] is None:
  359. # The ID already exists, but nothing in the values changed, so do nothing.
  360. pass
  361. quality_and_definition_changes_instance_containers.append(instance_container)
  362. else:
  363. existing_container = self._container_registry.findInstanceContainers(id = container_id)
  364. if not existing_container:
  365. containers_to_add.append(instance_container)
  366. if global_stack_need_rename:
  367. if instance_container.getMetaDataEntry("machine"):
  368. instance_container.setMetaDataEntry("machine", global_stack_id_new)
  369. # Add all the containers right before we try to add / serialize the stack
  370. for container in containers_to_add:
  371. self._container_registry.addContainer(container)
  372. container.setDirty(True)
  373. # Get the stack(s) saved in the workspace.
  374. Logger.log("d", "Workspace loading is checking stacks containers...")
  375. # load extruder stack files
  376. try:
  377. for index, extruder_stack_file in enumerate(extruder_stack_files):
  378. container_id = self._stripFileToId(extruder_stack_file)
  379. container_stacks = self._container_registry.findContainerStacks(id = container_id)
  380. if container_stacks:
  381. # this container stack already exists, try to resolve
  382. stack = container_stacks[0]
  383. if self._resolve_strategies["machine"] == "override":
  384. pass # do nothing
  385. elif self._resolve_strategies["machine"] == "new":
  386. # create a new extruder stack from this one
  387. new_id = self.getNewId(container_id)
  388. stack = ExtruderStack(new_id)
  389. stack.deserialize(archive.open(extruder_stack_file).read().decode("utf-8"))
  390. # Ensure a unique ID and name
  391. stack._id = new_id
  392. self._container_registry.addContainer(stack)
  393. extruder_stacks_added.append(stack)
  394. else:
  395. if self._resolve_strategies["machine"] == "override":
  396. global_stacks = self._container_registry.findContainerStacks(id = global_stack_id_original)
  397. # deserialize new extruder stack over the current ones
  398. if global_stacks:
  399. old_extruder_stack_id = global_stacks[0].extruders[index].getId()
  400. # HACK delete file
  401. self._container_registry._deleteFiles(global_stacks[0].extruders[index])
  402. global_stacks[0].extruders[index].deserialize(archive.open(extruder_stack_file).read().decode("utf-8"))
  403. # HACK
  404. global_stacks[0]._extruders = global_stacks[0]._extruders[:2]
  405. # HACK update cache
  406. del self._container_registry._id_container_cache[old_extruder_stack_id]
  407. new_extruder_stack_id = global_stacks[0].extruders[index].getId()
  408. self._container_registry._id_container_cache[new_extruder_stack_id] = global_stacks[0].extruders[index]
  409. stack = global_stacks[0].extruders[index]
  410. else:
  411. Logger.log("w", "Could not find global stack, while I expected it: %s" % global_stack_id_original)
  412. elif self._resolve_strategies["machine"] == "new":
  413. # container not found, create a new one
  414. stack = ExtruderStack(container_id)
  415. stack.deserialize(archive.open(extruder_stack_file).read().decode("utf-8"))
  416. self._container_registry.addContainer(stack)
  417. extruder_stacks_added.append(stack)
  418. else:
  419. Logger.log("w", "Unknown resolve strategy: %s" % str(self._resolve_strategies["machine"]))
  420. if global_stack_need_rename:
  421. if stack.getMetaDataEntry("machine"):
  422. stack.setMetaDataEntry("machine", global_stack_id_new)
  423. extruder_stacks.append(stack)
  424. except:
  425. Logger.logException("w", "We failed to serialize the stack. Trying to clean up.")
  426. # Something went really wrong. Try to remove any data that we added.
  427. for container in extruder_stacks:
  428. self._container_registry.removeContainer(container.getId())
  429. return None
  430. # load global stack file
  431. try:
  432. # Check if a stack by this ID already exists;
  433. container_stacks = self._container_registry.findContainerStacks(id = global_stack_id_original)
  434. if container_stacks:
  435. stack = container_stacks[0]
  436. if self._resolve_strategies["machine"] == "override":
  437. # TODO: HACK
  438. # There is a machine, check if it has authentication data. If so, keep that data.
  439. network_authentication_id = container_stacks[0].getMetaDataEntry("network_authentication_id")
  440. network_authentication_key = container_stacks[0].getMetaDataEntry("network_authentication_key")
  441. container_stacks[0].deserialize(archive.open(global_stack_file).read().decode("utf-8"))
  442. if network_authentication_id:
  443. container_stacks[0].addMetaDataEntry("network_authentication_id", network_authentication_id)
  444. if network_authentication_key:
  445. container_stacks[0].addMetaDataEntry("network_authentication_key", network_authentication_key)
  446. elif self._resolve_strategies["machine"] == "new":
  447. stack = GlobalStack(global_stack_id_new)
  448. stack.deserialize(archive.open(global_stack_file).read().decode("utf-8"))
  449. # Ensure a unique ID and name
  450. stack._id = global_stack_id_new
  451. # Extruder stacks are "bound" to a machine. If we add the machine as a new one, the id of the
  452. # bound machine also needs to change.
  453. if stack.getMetaDataEntry("machine", None):
  454. stack.setMetaDataEntry("machine", global_stack_id_new)
  455. # Only machines need a new name, stacks may be non-unique
  456. stack.setName(self._container_registry.uniqueName(stack.getName()))
  457. container_stacks_added.append(stack)
  458. self._container_registry.addContainer(stack)
  459. else:
  460. Logger.log("w", "Resolve strategy of %s for machine is not supported", self._resolve_strategies["machine"])
  461. else:
  462. # no existing container stack, so we create a new one
  463. stack = GlobalStack(global_stack_id_new)
  464. # Deserialize stack by converting read data from bytes to string
  465. stack.deserialize(archive.open(global_stack_file).read().decode("utf-8"))
  466. container_stacks_added.append(stack)
  467. self._container_registry.addContainer(stack)
  468. global_stack = stack
  469. Job.yieldThread()
  470. except:
  471. Logger.logException("w", "We failed to serialize the stack. Trying to clean up.")
  472. # Something went really wrong. Try to remove any data that we added.
  473. for container in containers_to_add:
  474. self._container_registry.removeContainer(container.getId())
  475. for container in container_stacks_added:
  476. self._container_registry.removeContainer(container.getId())
  477. for container in extruder_stacks_added:
  478. self._container_registry.removeContainer(container.getId())
  479. return None
  480. if self._resolve_strategies["machine"] == "new":
  481. # A new machine was made, but it was serialized with the wrong user container. Fix that now.
  482. for container in user_instance_containers:
  483. extruder_id = container.getMetaDataEntry("extruder", None)
  484. if extruder_id:
  485. for extruder in extruder_stacks:
  486. if extruder.getId() == extruder_id:
  487. extruder.replaceContainer(0, container)
  488. continue
  489. machine_id = container.getMetaDataEntry("machine", None)
  490. if machine_id:
  491. if global_stack.getId() == machine_id:
  492. global_stack.replaceContainer(0, container)
  493. continue
  494. for container_type in ("quality_changes", "definition_changes"):
  495. if self._resolve_strategies[container_type] == "new":
  496. # Quality changes needs to get a new ID, added to registry and to the right stacks
  497. for container in quality_and_definition_changes_instance_containers:
  498. old_id = container.getId()
  499. container.setName(self._container_registry.uniqueName(container.getName()))
  500. # We're not really supposed to change the ID in normal cases, but this is an exception.
  501. container._id = self.getNewId(container.getId())
  502. # The container was not added yet, as it didn't have an unique ID. It does now, so add it.
  503. self._container_registry.addContainer(container)
  504. # Replace the quality/definition changes container
  505. if container_type == "quality_changes":
  506. old_container = global_stack.qualityChanges
  507. elif container_type == "definition_changes":
  508. old_container = global_stack.definitionChanges
  509. # old_container = global_stack.findContainer({"type": container_type})
  510. if old_container.getId() == old_id:
  511. changes_index = global_stack.getContainerIndex(old_container)
  512. global_stack.replaceContainer(changes_index, container)
  513. continue
  514. for stack in extruder_stacks:
  515. old_container = stack.findContainer({"type": container_type})
  516. if old_container.getId() == old_id:
  517. changes_index = stack.getContainerIndex(old_container)
  518. stack.replaceContainer(changes_index, container)
  519. if self._resolve_strategies["material"] == "new":
  520. for material in material_containers:
  521. old_material = global_stack.findContainer({"type": "material"})
  522. if old_material.getId() in self._id_mapping:
  523. material_index = global_stack.getContainerIndex(old_material)
  524. global_stack.replaceContainer(material_index, material)
  525. continue
  526. for stack in extruder_stacks:
  527. old_material = stack.findContainer({"type": "material"})
  528. if old_material.getId() in self._id_mapping:
  529. material_index = stack.getContainerIndex(old_material)
  530. stack.replaceContainer(material_index, material)
  531. continue
  532. if extruder_stacks:
  533. for stack in extruder_stacks:
  534. ExtruderManager.getInstance().registerExtruder(stack, global_stack.getId())
  535. else:
  536. # Machine has no extruders, but it needs to be registered with the extruder manager.
  537. ExtruderManager.getInstance().registerExtruder(None, global_stack.getId())
  538. Logger.log("d", "Workspace loading is notifying rest of the code of changes...")
  539. if self._resolve_strategies["machine"] == "new":
  540. for stack in extruder_stacks:
  541. stack.setNextStack(global_stack)
  542. stack.containersChanged.emit(stack.getTop())
  543. # Actually change the active machine.
  544. Application.getInstance().setGlobalContainerStack(global_stack)
  545. # Notify everything/one that is to notify about changes.
  546. global_stack.containersChanged.emit(global_stack.getTop())
  547. # Load all the nodes / meshdata of the workspace
  548. nodes = self._3mf_mesh_reader.read(file_name)
  549. if nodes is None:
  550. nodes = []
  551. return nodes
  552. def _stripFileToId(self, file):
  553. mime_type = MimeTypeDatabase.getMimeTypeForFile(file)
  554. file = mime_type.stripExtension(file)
  555. return file.replace("Cura/", "")
  556. def _getXmlProfileClass(self):
  557. return self._container_registry.getContainerForMimeType(MimeTypeDatabase.getMimeType("application/x-ultimaker-material-profile"))
  558. ## Get the list of ID's of all containers in a container stack by partially parsing it's serialized data.
  559. def _getContainerIdListFromSerialized(self, serialized):
  560. parser = configparser.ConfigParser(interpolation=None, empty_lines_in_values=False)
  561. parser.read_string(serialized)
  562. container_ids = []
  563. if "containers" in parser:
  564. for index, container_id in parser.items("containers"):
  565. container_ids.append(container_id)
  566. elif parser.has_option("general", "containers"):
  567. container_string = parser["general"].get("containers", "")
  568. container_list = container_string.split(",")
  569. container_ids = [container_id for container_id in container_list if container_id != ""]
  570. return container_ids
  571. def _getMachineNameFromSerializedStack(self, serialized):
  572. parser = configparser.ConfigParser(interpolation=None, empty_lines_in_values=False)
  573. parser.read_string(serialized)
  574. return parser["general"].get("name", "")
  575. def _getMaterialLabelFromSerialized(self, serialized):
  576. data = ET.fromstring(serialized)
  577. metadata = data.iterfind("./um:metadata/um:name/um:label", {"um": "http://www.ultimaker.com/material"})
  578. for entry in metadata:
  579. return entry.text
  580. pass