From bf8fcd4ecb863d8c3cf5ee5411ae5c019ce0836b Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Tue, 29 Jul 2025 10:53:05 +0200 Subject: [PATCH] Find global stacks explicitly CURA-12016 Add a method to load instances of GlobalStack instead of loading all the ContainerStack and then filtering them. This makes the code more explicit and saves some time. --- cura/CuraPackageManager.py | 4 ++-- cura/Machines/ContainerTree.py | 24 +++++++++++------------- cura/Settings/CuraContainerRegistry.py | 26 ++++++++++++++++++++++++-- 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index e6879c3294..eb98a06e06 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -10,6 +10,7 @@ from UM.Logger import Logger from UM.PluginRegistry import PluginRegistry from cura.CuraApplication import CuraApplication # To find some resource types. from cura.Settings.GlobalStack import GlobalStack +from cura.Settings.CuraContainerRegistry import CuraContainerRegistry from UM.PackageManager import PackageManager # The class we're extending. from UM.Resources import Resources # To find storage paths for some resource types. @@ -123,8 +124,7 @@ class CuraPackageManager(PackageManager): """ ids = self.getPackageContainerIds(package_id) - container_stacks = self._application.getContainerRegistry().findContainerStacks() - global_stacks = [container_stack for container_stack in container_stacks if isinstance(container_stack, GlobalStack)] + global_stacks = CuraContainerRegistry.getInstance().findGlobalStacks() machine_with_materials = [] machine_with_qualities = [] for container_id in ids: diff --git a/cura/Machines/ContainerTree.py b/cura/Machines/ContainerTree.py index 904f66e96e..4c78152ca2 100644 --- a/cura/Machines/ContainerTree.py +++ b/cura/Machines/ContainerTree.py @@ -1,6 +1,5 @@ # Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. - from UM.Job import Job # For our background task of loading MachineNodes lazily. from UM.JobQueue import JobQueue # For our background task of loading MachineNodes lazily. from UM.Logger import Logger @@ -10,7 +9,7 @@ import cura.CuraApplication # Imported like this to prevent circular dependenci from cura.Machines.MachineNode import MachineNode from cura.Settings.GlobalStack import GlobalStack # To listen only to global stacks being added. -from typing import Dict, List, Optional, TYPE_CHECKING +from typing import Dict, List, Optional, Any, Set, TYPE_CHECKING import time if TYPE_CHECKING: @@ -78,9 +77,10 @@ class ContainerTree: def _onStartupFinished(self) -> None: """Ran after completely starting up the application.""" - - currently_added = ContainerRegistry.getInstance().findContainerStacks() # Find all currently added global stacks. - JobQueue.getInstance().add(self._MachineNodeLoadJob(self, currently_added)) + from Settings.CuraContainerRegistry import CuraContainerRegistry + currently_added_stacks = CuraContainerRegistry.getInstance().findGlobalStacks() + definitions_ids = set([stack.definition.getId() for stack in currently_added_stacks]) + JobQueue.getInstance().add(self._MachineNodeLoadJob(self, definitions_ids)) class _MachineNodeMap: """Dictionary-like object that contains the machines. @@ -153,17 +153,17 @@ class ContainerTree: faster. """ - def __init__(self, tree_root: "ContainerTree", container_stacks: List["ContainerStack"]) -> None: + def __init__(self, tree_root: "ContainerTree", definitions_ids: Set[str]) -> None: """Creates a new background task. :param tree_root: The container tree instance. This cannot be obtained through the singleton static function since the instance may not yet be constructed completely. - :param container_stacks: All of the stacks to pre-load the container trees for. This needs to be provided - from here because the stacks need to be constructed on the main thread because they are QObject. + :param definitions_ids: The IDs of all the definitions to pre-load the container trees for. This needs to be + provided from here because the stacks need to be constructed on the main thread because they are QObject. """ self.tree_root = tree_root - self.container_stacks = container_stacks + self.definitions_ids: Set[str] = definitions_ids super().__init__() def run(self) -> None: @@ -172,14 +172,12 @@ class ContainerTree: The ``JobQueue`` will schedule this on a different thread. """ Logger.log("d", "Started background loading of MachineNodes") - for stack in self.container_stacks: # Load all currently-added containers. - if not isinstance(stack, GlobalStack): - continue + for definition_id in self.definitions_ids: # Load all currently-added containers. # Allow a thread switch after every container. # Experimentally, sleep(0) didn't allow switching. sleep(0.1) or sleep(0.2) neither. # We're in no hurry though. Half a second is fine. time.sleep(0.5) - definition_id = stack.definition.getId() + if not self.tree_root.machines.is_loaded(definition_id): _ = self.tree_root.machines[definition_id] Logger.log("d", "All MachineNode loading completed") \ No newline at end of file diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index 1a711ef919..3e8c4d4375 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -23,8 +23,8 @@ from UM.Resources import Resources from UM.Util import parseBool from cura.ReaderWriters.ProfileWriter import ProfileWriter -from . import ExtruderStack -from . import GlobalStack +from cura.Settings import ExtruderStack +from cura.Settings import GlobalStack import cura.CuraApplication from cura.Settings.cura_empty_instance_containers import empty_quality_container @@ -52,6 +52,28 @@ class CuraContainerRegistry(ContainerRegistry): self._database_handlers["quality"] = QualityDatabaseHandler() self._database_handlers["intent"] = IntentDatabaseHandler() + def findGlobalStacks(self, **kwargs: Any) -> List[GlobalStack.GlobalStack]: + """Find all GlobalStack objects matching certain criteria. + + :param kwargs: A dictionary of keyword arguments containing + keys and values that need to match the metadata of the GlobalStack. + An asterisk in the values can be used to denote a wildcard. + """ + + return cast(List[GlobalStack.GlobalStack], self.findContainers(container_type = GlobalStack.GlobalStack, **kwargs)) + + def findGlobalStacksMetadata(self, **kwargs: Any) -> List[Dict[str, Any]]: + """Find the metadata of all global stacks matching certain criteria. + + :param kwargs: A dictionary of keyword arguments containing keys and + values that need to match the metadata. An asterisk in the values can be + used to denote a wildcard. + :return: A list of metadata dictionaries matching the search criteria, or + an empty list if nothing was found. + """ + + return self.findContainersMetadata(container_type = GlobalStack.GlobalStack, **kwargs) + @override(ContainerRegistry) def addContainer(self, container: ContainerInterface) -> bool: """Overridden from ContainerRegistry