Merge pull request #6542 from Ultimaker/CURA-6793_performance

Performance improvements
This commit is contained in:
ninovanhooff 2019-10-23 16:50:04 +02:00 committed by GitHub
commit b9b086a8c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 145 additions and 163 deletions

View File

@ -126,10 +126,6 @@ class BuildVolume(SceneNode):
# Therefore this works.
self._machine_manager.activeQualityChanged.connect(self._onStackChanged)
# This should also ways work, and it is semantically more correct,
# but it does not update the disallowed areas after material change
self._machine_manager.activeStackChanged.connect(self._onStackChanged)
# Enable and disable extruder
self._machine_manager.extruderChanged.connect(self.updateNodeBoundaryCheck)

View File

@ -1,21 +1,22 @@
# 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
from UM.Settings.ContainerRegistry import ContainerRegistry # To listen to containers being added.
from UM.Settings.Interfaces import ContainerInterface
from UM.Signal import Signal
import cura.CuraApplication # Imported like this to prevent circular dependencies.
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, TYPE_CHECKING
from typing import Dict, List, Optional, TYPE_CHECKING
import time
import UM.FlameProfiler
if TYPE_CHECKING:
from cura.Machines.QualityGroup import QualityGroup
from cura.Machines.QualityChangesGroup import QualityChangesGroup
from UM.Settings.ContainerStack import ContainerStack
## This class contains a look-up tree for which containers are available at
@ -38,12 +39,9 @@ class ContainerTree:
return cls.__instance
def __init__(self) -> None:
self.machines = {} # type: Dict[str, MachineNode] # Mapping from definition ID to machine nodes.
self.machines = self._MachineNodeMap() # Mapping from definition ID to machine nodes with lazy loading.
self.materialsChanged = Signal() # Emitted when any of the material nodes in the tree got changed.
container_registry = ContainerRegistry.getInstance()
container_registry.containerAdded.connect(self._machineAdded)
self._loadAll()
cura.CuraApplication.CuraApplication.getInstance().initializationFinished.connect(self._onStartupFinished) # Start the background task to load more machine nodes after start-up is completed.
## Get the quality groups available for the currently activated printer.
#
@ -76,54 +74,85 @@ class ContainerTree:
extruder_enabled = [extruder.isEnabled for extruder in global_stack.extruderList]
return self.machines[global_stack.definition.getId()].getQualityChangesGroups(variant_names, material_bases, extruder_enabled)
# Add a machine node by the id of it's definition.
# This is automatically called by the _machineAdded function, but it's sometimes needed to add a machine node
# faster than would have been done when waiting on any signals (for instance; when creating an entirely new machine)
@UM.FlameProfiler.profile
def addMachineNodeByDefinitionId(self, definition_id: str) -> None:
if definition_id in self.machines:
return # Already have this definition ID.
## Ran after completely starting up the application.
def _onStartupFinished(self):
currently_added = ContainerRegistry.getInstance().findContainerStacks() # Find all currently added global stacks.
JobQueue.getInstance().add(self._MachineNodeLoadJob(self, currently_added))
## Dictionary-like object that contains the machines.
#
# This handles the lazy loading of MachineNodes.
class _MachineNodeMap:
def __init__(self) -> None:
self._machines = {} # type: Dict[str, MachineNode]
## Returns whether a printer with a certain definition ID exists. This
# is regardless of whether or not the printer is loaded yet.
# \param definition_id The definition to look for.
# \return Whether or not a printer definition exists with that name.
def __contains__(self, definition_id: str) -> bool:
return len(ContainerRegistry.getInstance().findContainersMetadata(id = definition_id)) > 0
## Returns a machine node for the specified definition ID.
#
# If the machine node wasn't loaded yet, this will load it lazily.
# \param definition_id The definition to look for.
# \return A machine node for that definition.
def __getitem__(self, definition_id: str) -> MachineNode:
if definition_id not in self._machines:
start_time = time.time()
self.machines[definition_id] = MachineNode(definition_id)
self.machines[definition_id].materialsChanged.connect(self.materialsChanged)
self._machines[definition_id] = MachineNode(definition_id)
self._machines[definition_id].materialsChanged.connect(ContainerTree.getInstance().materialsChanged)
Logger.log("d", "Adding container tree for {definition_id} took {duration} seconds.".format(definition_id = definition_id, duration = time.time() - start_time))
return self._machines[definition_id]
## Builds the initial container tree.
def _loadAll(self):
Logger.log("i", "Building container tree.")
start_time = time.time()
all_stacks = ContainerRegistry.getInstance().findContainerStacks()
for stack in all_stacks:
## Gets a machine node for the specified definition ID, with default.
#
# The default is returned if there is no definition with the specified
# ID. If the machine node wasn't loaded yet, this will load it lazily.
# \param definition_id The definition to look for.
# \param default The machine node to return if there is no machine
# with that definition (can be ``None`` optionally or if not
# provided).
# \return A machine node for that definition, or the default if there
# is no definition with the provided definition_id.
def get(self, definition_id: str, default: Optional[MachineNode] = None) -> Optional[MachineNode]:
if definition_id not in self:
return default
return self[definition_id]
## Returns whether we've already cached this definition's node.
# \param definition_id The definition that we may have cached.
# \return ``True`` if it's cached.
def is_loaded(self, definition_id: str) -> bool:
return definition_id in self._machines
## Pre-loads all currently added printers as a background task so that
# switching printers in the interface is faster.
class _MachineNodeLoadJob(Job):
## 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.
def __init__(self, tree_root: "ContainerTree", container_stacks: List["ContainerStack"]):
self.tree_root = tree_root
self.container_stacks = container_stacks
super().__init__()
## Starts the background task.
#
# The ``JobQueue`` will schedule this on a different thread.
def run(self) -> None:
for stack in self.container_stacks: # Load all currently-added containers.
if not isinstance(stack, GlobalStack):
continue # Only want to load global stacks. We don't need to create a tree for extruder definitions.
continue
# 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 definition_id not in self.machines:
definition_start_time = time.time()
self.machines[definition_id] = MachineNode(definition_id)
self.machines[definition_id].materialsChanged.connect(self.materialsChanged)
Logger.log("d", "Adding container tree for {definition_id} took {duration} seconds.".format(definition_id = definition_id, duration = time.time() - definition_start_time))
Logger.log("d", "Building the container tree took %s seconds", time.time() - start_time)
## When a printer gets added, we need to build up the tree for that container.
def _machineAdded(self, container_stack: ContainerInterface) -> None:
if not isinstance(container_stack, GlobalStack):
return # Not our concern.
self.addMachineNodeByDefinitionId(container_stack.definition.getId())
## For debugging purposes, visualise the entire container tree as it stands
# now.
def _visualise_tree(self) -> str:
lines = ["% CONTAINER TREE"] # Start with array and then combine into string, for performance.
for machine in self.machines.values():
lines.append(" # " + machine.container_id)
for variant in machine.variants.values():
lines.append(" * " + variant.container_id)
for material in variant.materials.values():
lines.append(" + " + material.container_id)
for quality in material.qualities.values():
lines.append(" - " + quality.container_id)
for intent in quality.intents.values():
lines.append(" . " + intent.container_id)
return "\n".join(lines)
if not self.tree_root.machines.is_loaded(definition_id):
_ = self.tree_root.machines[definition_id]

View File

@ -177,5 +177,7 @@ class MachineNode(ContainerNode):
global_qualities = container_registry.findInstanceContainersMetadata(type = "quality", definition = self.quality_definition, global_quality = "True") # First try specific to this printer.
if len(global_qualities) == 0: # This printer doesn't override the global qualities.
global_qualities = container_registry.findInstanceContainersMetadata(type = "quality", definition = "fdmprinter", global_quality = "True") # Otherwise pick the global global qualities.
if len(global_qualities) == 0: # There are no global qualities either?! Something went very wrong, but we'll not crash and properly fill the tree.
global_qualities = [cura.CuraApplication.CuraApplication.getInstance().empty_quality_container.getMetaData()]
for global_quality in global_qualities:
self.global_qualities[global_quality["quality_type"]] = QualityNode(global_quality["id"], parent = self)

View File

@ -53,7 +53,7 @@ class BaseMaterialsModel(ListModel):
self._machine_manager.globalContainerChanged.connect(self._updateExtruderStack)
self._updateExtruderStack()
# Update this model when switching machines, when adding materials or changing their metadata.
# Update this model when switching machines or tabs, when adding materials or changing their metadata.
self._machine_manager.activeStackChanged.connect(self._onChanged)
ContainerTree.getInstance().materialsChanged.connect(self._materialsListChanged)
self._application.getMaterialManagementModel().favoritesChanged.connect(self._onChanged)
@ -155,7 +155,7 @@ class BaseMaterialsModel(ListModel):
nozzle_name = extruder_stack.variant.getName()
materials = ContainerTree.getInstance().machines[global_stack.definition.getId()].variants[nozzle_name].materials
approximate_material_diameter = extruder_stack.getApproximateMaterialDiameter()
self._available_materials = {key: material for key, material in materials.items() if float(material.container.getMetaDataEntry("approximate_diameter")) == approximate_material_diameter}
self._available_materials = {key: material for key, material in materials.items() if float(material.getMetaDataEntry("approximate_diameter", -1)) == approximate_material_diameter}
## This method is used by all material models in the beginning of the
# _update() method in order to prevent errors. It's the same in all models

View File

@ -27,7 +27,7 @@ class FavoriteMaterialsModel(BaseMaterialsModel):
for root_material_id, container_node in self._available_materials.items():
# Do not include the materials from a to-be-removed package
if bool(container_node.container.getMetaDataEntry("removed", False)):
if bool(container_node.getMetaDataEntry("removed", False)):
continue
# Only add results for favorite materials

View File

@ -18,11 +18,11 @@ class GenericMaterialsModel(BaseMaterialsModel):
for root_material_id, container_node in self._available_materials.items():
# Do not include the materials from a to-be-removed package
if bool(container_node.container.getMetaDataEntry("removed", False)):
if bool(container_node.getMetaDataEntry("removed", False)):
continue
# Only add results for generic materials
if container_node.container.getMetaDataEntry("brand", "unknown").lower() != "generic":
if container_node.getMetaDataEntry("brand", "unknown").lower() != "generic":
continue
item = self._createMaterialItem(root_material_id, container_node)

View File

@ -61,11 +61,9 @@ class IntentCategoryModel(ListModel):
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerChange)
ContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChange)
machine_manager = application.getMachineManager()
machine_manager.globalContainerChanged.connect(self.update)
machine_manager.activeQualityGroupChanged.connect(self.update)
machine_manager.activeStackChanged.connect(self.update)
machine_manager = cura.CuraApplication.CuraApplication.getInstance().getMachineManager()
machine_manager.activeMaterialChanged.connect(self.update)
machine_manager.activeVariantChanged.connect(self.update)
machine_manager.extruderChanged.connect(self.update)
extruder_manager = application.getExtruderManager()

View File

@ -37,18 +37,18 @@ class MaterialBrandsModel(BaseMaterialsModel):
# Part 1: Generate the entire tree of brands -> material types -> spcific materials
for root_material_id, container_node in self._available_materials.items():
# Do not include the materials from a to-be-removed package
if bool(container_node.container.getMetaDataEntry("removed", False)):
if bool(container_node.getMetaDataEntry("removed", False)):
continue
# Add brands we haven't seen yet to the dict, skipping generics
brand = container_node.container.getMetaDataEntry("brand", "")
brand = container_node.getMetaDataEntry("brand", "")
if brand.lower() == "generic":
continue
if brand not in brand_group_dict:
brand_group_dict[brand] = {}
# Add material types we haven't seen yet to the dict
material_type = container_node.container.getMetaDataEntry("material", "")
material_type = container_node.getMetaDataEntry("material", "")
if material_type not in brand_group_dict[brand]:
brand_group_dict[brand][material_type] = []

View File

@ -3,9 +3,9 @@
from PyQt5.QtCore import Qt
from UM.Application import Application
from UM.Logger import Logger
from UM.Qt.ListModel import ListModel
import cura.CuraApplication # Imported like this to prevent circular dependencies.
from cura.Machines.ContainerTree import ContainerTree
@ -21,16 +21,13 @@ class NozzleModel(ListModel):
self.addRoleName(self.HotendNameRole, "hotend_name")
self.addRoleName(self.ContainerNodeRole, "container_node")
self._application = Application.getInstance()
self._machine_manager = self._application.getMachineManager()
self._machine_manager.globalContainerChanged.connect(self._update)
cura.CuraApplication.CuraApplication.getInstance().getMachineManager().globalContainerChanged.connect(self._update)
self._update()
def _update(self):
Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
global_stack = self._machine_manager.activeMachine
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
if global_stack is None:
self.setItems([])
return

View File

@ -40,7 +40,8 @@ class QualityProfilesDropDownMenuModel(ListModel):
application.globalContainerStackChanged.connect(self._onChange)
machine_manager.activeQualityGroupChanged.connect(self._onChange)
machine_manager.activeStackChanged.connect(self._onChange)
machine_manager.activeMaterialChanged.connect(self._onChange)
machine_manager.activeVariantChanged.connect(self._onChange)
machine_manager.extruderChanged.connect(self._onChange)
extruder_manager = application.getExtruderManager()

View File

@ -68,7 +68,7 @@ class QualityGroup:
if not node.container:
Logger.log("w", "Node {0} doesn't have a container.".format(node.container_id))
return
is_experimental = parseBool(node.container.getMetaDataEntry("is_experimental", False))
is_experimental = parseBool(node.getMetaDataEntry("is_experimental", False))
self.is_experimental |= is_experimental
def setExtruderNode(self, position: int, node: "ContainerNode") -> None:
@ -78,5 +78,5 @@ class QualityGroup:
if not node.container:
Logger.log("w", "Node {0} doesn't have a container.".format(node.container_id))
return
is_experimental = parseBool(node.container.getMetaDataEntry("is_experimental", False))
is_experimental = parseBool(node.getMetaDataEntry("is_experimental", False))
self.is_experimental |= is_experimental

View File

@ -85,7 +85,7 @@ class ContainerManager(QObject):
if container_node.container is None:
Logger.log("w", "Container node {0} doesn't have a container.".format(container_node.container_id))
return False
root_material_id = container_node.container.getMetaDataEntry("base_file", "")
root_material_id = container_node.getMetaDataEntry("base_file", "")
container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry()
if container_registry.isReadOnly(root_material_id):
Logger.log("w", "Cannot set metadata of read-only container %s.", root_material_id)
@ -350,8 +350,7 @@ class ContainerManager(QObject):
@pyqtSlot("QVariant")
def unlinkMaterial(self, material_node: "MaterialNode") -> None:
# Get the material group
if material_node.container is None:
Logger.log("w", "Material node {0} doesn't have a container.".format(material_node.container_id))
if material_node.container is None: # Failed to lazy-load this container.
return
root_material_query = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry().findInstanceContainers(id = material_node.getMetaDataEntry("base_file", ""))
if not root_material_query:

View File

@ -37,10 +37,6 @@ class CuraStackBuilder:
return None
machine_definition = definitions[0]
# The container tree listens to the containerAdded signal to add the definition and build the tree,
# but that signal is emitted with a delay which might not have passed yet.
# Therefore we must make sure that it's manually added here.
container_tree.addMachineNodeByDefinitionId(machine_definition.getId())
machine_node = container_tree.machines[machine_definition.getId()]
generated_name = registry.createUniqueName("machine", "", name, machine_definition.getName())

View File

@ -1,4 +1,4 @@
# Copyright (c) 2018 Ultimaker B.V.
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant # For communicating data and events to Qt.
@ -42,8 +42,6 @@ class ExtruderManager(QObject):
# TODO; I have no idea why this is a union of ID's and extruder stacks. This needs to be fixed at some point.
self._selected_object_extruders = [] # type: List[Union[str, "ExtruderStack"]]
self._addCurrentMachineExtruders()
Selection.selectionChanged.connect(self.resetSelectedObjectExtruders)
## Signal to notify other components when the list of extruders for a machine definition changes.
@ -311,12 +309,9 @@ class ExtruderManager(QObject):
self.resetSelectedObjectExtruders()
## Adds the extruders of the currently active machine.
def _addCurrentMachineExtruders(self) -> None:
global_stack = self._application.getGlobalContainerStack()
## Adds the extruders to the selected machine.
def addMachineExtruders(self, global_stack: GlobalStack) -> None:
extruders_changed = False
if global_stack:
container_registry = ContainerRegistry.getInstance()
global_stack_id = global_stack.getId()
@ -341,8 +336,6 @@ class ExtruderManager(QObject):
self.fixSingleExtrusionMachineExtruderDefinition(global_stack)
if extruders_changed:
self.extrudersChanged.emit(global_stack_id)
self.setActiveExtruderIndex(0)
self.activeExtruderChanged.emit()
# After 3.4, all single-extrusion machines have their own extruder definition files instead of reusing
# "fdmextruder". We need to check a machine here so its extruder definition is correct according to this.

View File

@ -139,8 +139,8 @@ class MachineManager(QObject):
activeVariantChanged = pyqtSignal()
activeQualityChanged = pyqtSignal()
activeIntentChanged = pyqtSignal()
activeStackChanged = pyqtSignal() # Emitted whenever the active stack is changed (ie: when changing between extruders, changing a profile, but not when changing a value)
extruderChanged = pyqtSignal()
activeStackChanged = pyqtSignal() # Emitted whenever the active extruder stack is changed (ie: when switching the active extruder tab or changing between printers)
extruderChanged = pyqtSignal() # Emitted whenever an extruder is activated or deactivated or the default extruder changes.
activeStackValueChanged = pyqtSignal() # Emitted whenever a value inside the active stack is changed.
activeStackValidationChanged = pyqtSignal() # Emitted whenever a validation inside active container is changed
@ -217,6 +217,7 @@ class MachineManager(QObject):
return 0
return len(general_definition_containers[0].getAllKeys())
## Triggered when the global container stack is changed in CuraApplication.
def _onGlobalContainerChanged(self) -> None:
if self._global_container_stack:
try:
@ -268,11 +269,7 @@ class MachineManager(QObject):
def _onActiveExtruderStackChanged(self) -> None:
self.blurSettings.emit() # Ensure no-one has focus.
if self._active_container_stack is not None:
self._active_container_stack.pyqtContainersChanged.disconnect(self.activeStackChanged) # Unplug from the old one.
self._active_container_stack = ExtruderManager.getInstance().getActiveExtruderStack()
if self._active_container_stack is not None:
self._active_container_stack.pyqtContainersChanged.connect(self.activeStackChanged) # Plug into the new one.
def __emitChangedSignals(self) -> None:
self.activeQualityChanged.emit()
@ -296,7 +293,6 @@ class MachineManager(QObject):
self.blurSettings.emit() # Ensure no-one has focus.
container_registry = CuraContainerRegistry.getInstance()
containers = container_registry.findContainerStacks(id = stack_id)
if not containers:
return
@ -306,21 +302,25 @@ class MachineManager(QObject):
# Make sure that the default machine actions for this machine have been added
self._application.getMachineActionManager().addDefaultMachineActions(global_stack)
ExtruderManager.getInstance().fixSingleExtrusionMachineExtruderDefinition(global_stack)
extruder_manager = ExtruderManager.getInstance()
extruder_manager.fixSingleExtrusionMachineExtruderDefinition(global_stack)
if not global_stack.isValid():
# Mark global stack as invalid
ConfigurationErrorMessage.getInstance().addFaultyContainers(global_stack.getId())
return # We're done here
self._global_container_stack = global_stack
extruder_manager.addMachineExtruders(global_stack)
self._application.setGlobalContainerStack(global_stack)
ExtruderManager.getInstance()._globalContainerStackChanged()
self._onGlobalContainerChanged()
# Switch to the first enabled extruder
self.updateDefaultExtruder()
default_extruder_position = int(self.defaultExtruderPosition)
ExtruderManager.getInstance().setActiveExtruderIndex(default_extruder_position)
old_active_extruder_index = extruder_manager.activeExtruderIndex
extruder_manager.setActiveExtruderIndex(default_extruder_position)
if old_active_extruder_index == default_extruder_position:
# This signal might not have been emitted yet (if it didn't change) but we still want the models to update that depend on it because we changed the contents of the containers too.
extruder_manager.activeExtruderChanged.emit()
self.__emitChangedSignals()

View File

@ -29,6 +29,7 @@ def createMockedStack(definition_id: str):
def container_registry():
result = MagicMock()
result.findContainerStacks = MagicMock(return_value = [createMockedStack("machine_1"), createMockedStack("machine_2")])
result.findContainersMetadata = lambda id: [{"id": id}] if id in {"machine_1", "machine_2"} else []
return result
@pytest.fixture
@ -37,42 +38,12 @@ def application():
def test_containerTreeInit(container_registry):
with patch("UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value=container_registry)):
with patch("UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value = container_registry)):
container_tree = ContainerTree()
assert "machine_1" in container_tree.machines
assert "machine_2" in container_tree.machines
def test_newMachineAdded(container_registry):
mocked_definition_container = MagicMock(spec=DefinitionContainer)
mocked_definition_container.getId = MagicMock(return_value = "machine_3")
with patch("UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value = container_registry)):
container_tree = ContainerTree()
# machine_3 shouldn't be there, as on init it wasn't in the registry
assert "machine_3" not in container_tree.machines
# It should only react when a globalStack is added.
container_tree._machineAdded(mocked_definition_container)
assert "machine_3" not in container_tree.machines
container_tree._machineAdded(createMockedStack("machine_3"))
assert "machine_3" in container_tree.machines
def test_alreadyKnownMachineAdded(container_registry):
mocked_definition_container = MagicMock(spec = DefinitionContainer)
mocked_definition_container.getId = MagicMock(return_value = "machine_2")
with patch("UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value = container_registry)):
container_tree = ContainerTree()
assert len(container_tree.machines) == 2
# The ID is already there, so no machine should be added.
container_tree._machineAdded(mocked_definition_container)
assert len(container_tree.machines) == 2
def test_getCurrentQualityGroupsNoGlobalStack(container_registry):
with patch("UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value = container_registry)):
with patch("cura.CuraApplication.CuraApplication.getInstance", MagicMock(return_value = MagicMock(getGlobalContainerStack = MagicMock(return_value = None)))):
@ -84,7 +55,7 @@ def test_getCurrentQualityGroupsNoGlobalStack(container_registry):
def test_getCurrentQualityGroups(container_registry, application):
with patch("UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value = container_registry)):
container_tree = ContainerTree()
container_tree.machines["current_global_stack"] = MagicMock() # Mock so that we can track whether the getQualityGroups function gets called with correct parameters.
container_tree.machines._machines["current_global_stack"] = MagicMock() # Mock so that we can track whether the getQualityGroups function gets called with correct parameters.
with patch("cura.CuraApplication.CuraApplication.getInstance", MagicMock(return_value = application)):
result = container_tree.getCurrentQualityGroups()
@ -108,7 +79,7 @@ def test_getCurrentQualityChangesGroupsNoGlobalStack(container_registry):
def test_getCurrentQualityChangesGroups(container_registry, application):
with patch("UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value = container_registry)):
container_tree = ContainerTree()
container_tree.machines["current_global_stack"] = MagicMock() # Mock so that we can track whether the getQualityGroups function gets called with correct parameters.
container_tree.machines._machines["current_global_stack"] = MagicMock() # Mock so that we can track whether the getQualityGroups function gets called with correct parameters.
with patch("cura.CuraApplication.CuraApplication.getInstance", MagicMock(return_value = application)):
result = container_tree.getCurrentQualityChangesGroups()