CuraPackageManager.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. # Copyright (c) 2018 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. import glob
  4. import os
  5. from pathlib import Path
  6. from typing import Any, cast, Dict, List, Set, Tuple, TYPE_CHECKING, Optional
  7. from UM.Logger import Logger
  8. from UM.PluginRegistry import PluginRegistry
  9. from cura.CuraApplication import CuraApplication # To find some resource types.
  10. from cura.Settings.GlobalStack import GlobalStack
  11. from UM.PackageManager import PackageManager # The class we're extending.
  12. from UM.Resources import Resources # To find storage paths for some resource types.
  13. from UM.i18n import i18nCatalog
  14. catalog = i18nCatalog("cura")
  15. if TYPE_CHECKING:
  16. from UM.Qt.QtApplication import QtApplication
  17. from PyQt6.QtCore import QObject
  18. class CuraPackageManager(PackageManager):
  19. def __init__(self, application: "QtApplication", parent: Optional["QObject"] = None) -> None:
  20. super().__init__(application, parent)
  21. self._local_packages: Optional[List[Dict[str, Any]]] = None
  22. self._local_packages_ids: Optional[Set[str]] = None
  23. self.installedPackagesChanged.connect(self._updateLocalPackages)
  24. def _updateLocalPackages(self) -> None:
  25. self._local_packages = self.getAllLocalPackages()
  26. self._local_packages_ids = set(pkg["package_id"] for pkg in self._local_packages)
  27. @property
  28. def local_packages(self) -> List[Dict[str, Any]]:
  29. """locally installed packages, lazy execution"""
  30. if self._local_packages is None:
  31. self._updateLocalPackages()
  32. # _updateLocalPackages always results in a list of packages, not None.
  33. # It's guaranteed to be a list now.
  34. return cast(List[Dict[str, Any]], self._local_packages)
  35. @property
  36. def local_packages_ids(self) -> Set[str]:
  37. """locally installed packages, lazy execution"""
  38. if self._local_packages_ids is None:
  39. self._updateLocalPackages()
  40. # _updateLocalPackages always results in a list of packages, not None.
  41. # It's guaranteed to be a list now.
  42. return cast(Set[str], self._local_packages_ids)
  43. def initialize(self) -> None:
  44. self._installation_dirs_dict["materials"] = Resources.getStoragePath(CuraApplication.ResourceTypes.MaterialInstanceContainer)
  45. self._installation_dirs_dict["qualities"] = Resources.getStoragePath(CuraApplication.ResourceTypes.QualityInstanceContainer)
  46. self._installation_dirs_dict["intents"] = Resources.getStoragePath(CuraApplication.ResourceTypes.IntentInstanceContainer)
  47. self._installation_dirs_dict["variants"] = Resources.getStoragePath(CuraApplication.ResourceTypes.VariantInstanceContainer)
  48. super().initialize()
  49. def isMaterialBundled(self, file_name: str, guid: str):
  50. """ Check if there is a bundled material name with file_name and guid """
  51. for path in Resources.getSecureSearchPaths():
  52. # Secure search paths are install directory paths, if a material is in here it must be bundled.
  53. paths = [Path(p) for p in glob.glob(path + '/**/*.xml.fdm_material', recursive=True)]
  54. for material in paths:
  55. if material.name == file_name:
  56. Logger.info(f"Found bundled material: {material.name}. Located in path: {str(material)}")
  57. with open(material, encoding="utf-8") as f:
  58. # Make sure the file we found has the same guid as our material
  59. # Parsing this xml would be better but the namespace is needed to search it.
  60. parsed_guid = PluginRegistry.getInstance().getPluginObject(
  61. "XmlMaterialProfile").getMetadataFromSerialized(
  62. f.read(), "GUID")
  63. if guid == parsed_guid:
  64. # The material we found matches both filename and GUID
  65. return True
  66. return False
  67. def getMaterialFilePackageId(self, file_name: str, guid: str) -> str:
  68. """Get the id of the installed material package that contains file_name"""
  69. for material_package in [f for f in os.scandir(self._installation_dirs_dict["materials"]) if f.is_dir()]:
  70. package_id = material_package.name
  71. for root, _, file_names in os.walk(material_package.path):
  72. if file_name not in file_names:
  73. # File with the name we are looking for is not in this directory
  74. continue
  75. with open(os.path.join(root, file_name), encoding="utf-8") as f:
  76. # Make sure the file we found has the same guid as our material
  77. # Parsing this xml would be better but the namespace is needed to search it.
  78. parsed_guid = PluginRegistry.getInstance().getPluginObject("XmlMaterialProfile").getMetadataFromSerialized(
  79. f.read(), "GUID")
  80. if guid == parsed_guid:
  81. return package_id
  82. Logger.error("Could not find package_id for file: {} with GUID: {} ".format(file_name, guid))
  83. Logger.error(f"Bundled paths searched: {list(Resources.getSecureSearchPaths())}")
  84. return ""
  85. def getMachinesUsingPackage(self, package_id: str) -> Tuple[List[Tuple[GlobalStack, str, str]], List[Tuple[GlobalStack, str, str]]]:
  86. """Returns a list of where the package is used
  87. It loops through all the package contents and see if some of the ids are used.
  88. :param package_id: package id to search for
  89. :return: empty if it is never used, otherwise a list consisting of 3-tuples
  90. """
  91. ids = self.getPackageContainerIds(package_id)
  92. container_stacks = self._application.getContainerRegistry().findContainerStacks()
  93. global_stacks = [container_stack for container_stack in container_stacks if isinstance(container_stack, GlobalStack)]
  94. machine_with_materials = []
  95. machine_with_qualities = []
  96. for container_id in ids:
  97. for global_stack in global_stacks:
  98. for extruder_nr, extruder_stack in enumerate(global_stack.extruderList):
  99. if container_id in (extruder_stack.material.getId(), extruder_stack.material.getMetaData().get("base_file")):
  100. machine_with_materials.append((global_stack, str(extruder_nr), container_id))
  101. if container_id == extruder_stack.quality.getId():
  102. machine_with_qualities.append((global_stack, str(extruder_nr), container_id))
  103. return machine_with_materials, machine_with_qualities
  104. def getAllLocalPackages(self) -> List[Dict[str, Any]]:
  105. """ Returns an unordered list of all the package_info of installed, to be installed, or bundled packages"""
  106. packages: List[Dict[str, Any]] = []
  107. for packages_to_add in self.getAllInstalledPackagesInfo().values():
  108. packages.extend(packages_to_add)
  109. return packages