diff --git a/cura/Machines/Models/BaseMaterialsModel.py b/cura/Machines/Models/BaseMaterialsModel.py index fc2f90f1e4..3ed23a2c9e 100644 --- a/cura/Machines/Models/BaseMaterialsModel.py +++ b/cura/Machines/Models/BaseMaterialsModel.py @@ -1,9 +1,9 @@ # Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import Optional, Dict, Set +from typing import Dict, Set -from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty +from PyQt5.QtCore import Qt, QTimer, pyqtSignal, pyqtProperty from UM.Qt.ListModel import ListModel @@ -38,14 +38,25 @@ class BaseMaterialsModel(ListModel): self._extruder_stack = None self._enabled = True + # CURA-6904 + # Updating the material model requires information from material nodes and containers. We use a timer here to + # make sure that an update function call will not be directly invoked by an event. Because the triggered event + # can be caused in the middle of a XMLMaterial loading, and the material container we try to find may not be + # in the system yet. This will cause an infinite recursion of (1) trying to load a material, (2) trying to + # update the material model, (3) cannot find the material container, load it, (4) repeat #1. + self._update_timer = QTimer() + self._update_timer.setInterval(100) + self._update_timer.setSingleShot(True) + self._update_timer.timeout.connect(self._update) + # Update the stack and the model data when the machine changes self._machine_manager.globalContainerChanged.connect(self._updateExtruderStack) self._updateExtruderStack() # Update this model when switching machines, when adding materials or changing their metadata. - self._machine_manager.activeStackChanged.connect(self._update) + self._machine_manager.activeStackChanged.connect(self._onChanged) ContainerTree.getInstance().materialsChanged.connect(self._materialsListChanged) - self._application.getMaterialManagementModel().favoritesChanged.connect(self._update) + self._application.getMaterialManagementModel().favoritesChanged.connect(self._onChanged) self.addRoleName(Qt.UserRole + 1, "root_material_id") self.addRoleName(Qt.UserRole + 2, "id") @@ -64,14 +75,17 @@ class BaseMaterialsModel(ListModel): self.addRoleName(Qt.UserRole + 15, "container_node") self.addRoleName(Qt.UserRole + 16, "is_favorite") + def _onChanged(self) -> None: + self._update_timer.start() + def _updateExtruderStack(self): global_stack = self._machine_manager.activeMachine if global_stack is None: return if self._extruder_stack is not None: - self._extruder_stack.pyqtContainersChanged.disconnect(self._update) - self._extruder_stack.approximateMaterialDiameterChanged.disconnect(self._update) + self._extruder_stack.pyqtContainersChanged.disconnect(self._onChanged) + self._extruder_stack.approximateMaterialDiameterChanged.disconnect(self._onChanged) try: self._extruder_stack = global_stack.extruderList[self._extruder_position] @@ -79,10 +93,10 @@ class BaseMaterialsModel(ListModel): self._extruder_stack = None if self._extruder_stack is not None: - self._extruder_stack.pyqtContainersChanged.connect(self._update) - self._extruder_stack.approximateMaterialDiameterChanged.connect(self._update) + self._extruder_stack.pyqtContainersChanged.connect(self._onChanged) + self._extruder_stack.approximateMaterialDiameterChanged.connect(self._onChanged) # Force update the model when the extruder stack changes - self._update() + self._onChanged() def setExtruderPosition(self, position: int): if self._extruder_stack is None or self._extruder_position != position: @@ -99,7 +113,7 @@ class BaseMaterialsModel(ListModel): self._enabled = enabled if self._enabled: # ensure the data is there again. - self._update() + self._onChanged() self.enabledChanged.emit() @pyqtProperty(bool, fset = setEnabled, notify = enabledChanged) @@ -119,12 +133,12 @@ class BaseMaterialsModel(ListModel): return if material.variant.machine.container_id != global_stack.definition.getId(): return - self._update() + self._onChanged() ## Triggered when the list of favorite materials is changed. def _favoritesChanged(self, material_base_file: str) -> None: if material_base_file in self._available_materials: - self._update() + self._onChanged() ## This is an abstract method that needs to be implemented by the specific # models themselves. diff --git a/cura/Machines/Models/FavoriteMaterialsModel.py b/cura/Machines/Models/FavoriteMaterialsModel.py index 255ef1dc0a..c5b245e400 100644 --- a/cura/Machines/Models/FavoriteMaterialsModel.py +++ b/cura/Machines/Models/FavoriteMaterialsModel.py @@ -9,14 +9,14 @@ class FavoriteMaterialsModel(BaseMaterialsModel): def __init__(self, parent = None): super().__init__(parent) cura.CuraApplication.CuraApplication.getInstance().getPreferences().preferenceChanged.connect(self._onFavoritesChanged) - self._update() + self._onChanged() ## Triggered when any preference changes, but only handles it when the list # of favourites is changed. def _onFavoritesChanged(self, preference_key: str) -> None: if preference_key != "cura/favorite_materials": return - self._update() + self._onChanged() def _update(self): if not self._canUpdate(): diff --git a/cura/Machines/Models/GenericMaterialsModel.py b/cura/Machines/Models/GenericMaterialsModel.py index 2542a6412a..00adf5636a 100644 --- a/cura/Machines/Models/GenericMaterialsModel.py +++ b/cura/Machines/Models/GenericMaterialsModel.py @@ -7,7 +7,7 @@ class GenericMaterialsModel(BaseMaterialsModel): def __init__(self, parent = None): super().__init__(parent) - self._update() + self._onChanged() def _update(self): if not self._canUpdate():