Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/Ultimaker/Cura.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/cura
diff options
context:
space:
mode:
authorNino van Hooff <ninovanhooff@gmail.com>2020-05-28 18:31:24 +0300
committerNino van Hooff <ninovanhooff@gmail.com>2020-05-28 18:31:24 +0300
commit58ffc9dcae0020d7dd4f3c32b41922dfdbef37d3 (patch)
tree54902883427fa76a9dffb5068afd99532563cb4b /cura
parentc2c96faf5fcbad942f8cf257e75c94a623ac5eaa (diff)
parent2a70813d030c678181b5c37fc82cb513d689187b (diff)
Merge remote-tracking branch 'origin/master' into doxygen_to_restructuredtext_comments
# Conflicts: # cura/API/__init__.py # cura/Settings/CuraContainerRegistry.py # cura/Settings/ExtruderManager.py # plugins/PostProcessingPlugin/scripts/PauseAtHeight.py # plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py # plugins/UM3NetworkPrinting/src/Cloud/ToolPathUploader.py # plugins/UM3NetworkPrinting/src/Network/LocalClusterOutputDeviceManager.py
Diffstat (limited to 'cura')
-rw-r--r--cura/API/Account.py57
-rw-r--r--cura/API/ConnectionStatus.py64
-rw-r--r--cura/API/__init__.py10
-rw-r--r--cura/CuraPackageManager.py6
-rw-r--r--cura/Machines/Models/BaseMaterialsModel.py5
-rw-r--r--cura/Machines/Models/DiscoveredPrintersModel.py29
-rw-r--r--cura/Machines/Models/GlobalStacksModel.py17
-rw-r--r--cura/Machines/Models/QualitySettingsModel.py2
-rw-r--r--cura/Scene/ConvexHullDecorator.py4
-rw-r--r--cura/Settings/CuraContainerRegistry.py28
-rw-r--r--cura/Settings/CuraStackBuilder.py2
-rwxr-xr-xcura/Settings/ExtruderManager.py55
-rw-r--r--cura/Settings/ExtruderStack.py3
-rwxr-xr-xcura/Settings/MachineManager.py32
14 files changed, 208 insertions, 106 deletions
diff --git a/cura/API/Account.py b/cura/API/Account.py
index 27adb1a0c8..581a91da04 100644
--- a/cura/API/Account.py
+++ b/cura/API/Account.py
@@ -23,6 +23,7 @@ class SyncState:
SYNCING = 0
SUCCESS = 1
ERROR = 2
+ IDLE = 3
class Account(QObject):
"""The account API provides a version-proof bridge to use Ultimaker Accounts
@@ -54,6 +55,7 @@ class Account(QObject):
"""
lastSyncDateTimeChanged = pyqtSignal()
syncStateChanged = pyqtSignal(int) # because SyncState is an int Enum
+ manualSyncEnabledChanged = pyqtSignal(bool)
def __init__(self, application: "CuraApplication", parent = None) -> None:
super().__init__(parent)
@@ -62,7 +64,8 @@ class Account(QObject):
self._error_message = None # type: Optional[Message]
self._logged_in = False
- self._sync_state = SyncState.SUCCESS
+ self._sync_state = SyncState.IDLE
+ self._manual_sync_enabled = False
self._last_sync_str = "-"
self._callback_port = 32118
@@ -110,16 +113,21 @@ class Account(QObject):
:param state: One of SyncState
"""
+ Logger.info("Service {service} enters sync state {state}", service = service_name, state = state)
+
prev_state = self._sync_state
self._sync_services[service_name] = state
if any(val == SyncState.SYNCING for val in self._sync_services.values()):
self._sync_state = SyncState.SYNCING
+ self._setManualSyncEnabled(False)
elif any(val == SyncState.ERROR for val in self._sync_services.values()):
self._sync_state = SyncState.ERROR
+ self._setManualSyncEnabled(True)
else:
self._sync_state = SyncState.SUCCESS
+ self._setManualSyncEnabled(False)
if self._sync_state != prev_state:
self.syncStateChanged.emit(self._sync_state)
@@ -162,11 +170,31 @@ class Account(QObject):
self._logged_in = logged_in
self.loginStateChanged.emit(logged_in)
if logged_in:
- self.sync()
+ self._setManualSyncEnabled(False)
+ self._sync()
else:
if self._update_timer.isActive():
self._update_timer.stop()
+ def _sync(self) -> None:
+ """Signals all sync services to start syncing
+
+ This can be considered a forced sync: even when a
+ sync is currently running, a sync will be requested.
+ """
+
+ if self._update_timer.isActive():
+ self._update_timer.stop()
+ elif self._sync_state == SyncState.SYNCING:
+ Logger.warning("Starting a new sync while previous sync was not completed\n{}", str(self._sync_services))
+
+ self.syncRequested.emit()
+
+ def _setManualSyncEnabled(self, enabled: bool) -> None:
+ if self._manual_sync_enabled != enabled:
+ self._manual_sync_enabled = enabled
+ self.manualSyncEnabledChanged.emit(enabled)
+
@pyqtSlot()
@pyqtSlot(bool)
def login(self, force_logout_before_login: bool = False) -> None:
@@ -217,20 +245,23 @@ class Account(QObject):
def lastSyncDateTime(self) -> str:
return self._last_sync_str
- @pyqtSlot()
- def sync(self) -> None:
- """Signals all sync services to start syncing
+ @pyqtProperty(bool, notify=manualSyncEnabledChanged)
+ def manualSyncEnabled(self) -> bool:
+ return self._manual_sync_enabled
- This can be considered a forced sync: even when a
- sync is currently running, a sync will be requested.
- """
+ @pyqtSlot()
+ @pyqtSlot(bool)
+ def sync(self, user_initiated: bool = False) -> None:
+ if user_initiated:
+ self._setManualSyncEnabled(False)
- if self._update_timer.isActive():
- self._update_timer.stop()
- elif self._sync_state == SyncState.SYNCING:
- Logger.warning("Starting a new sync while previous sync was not completed\n{}", str(self._sync_services))
+ self._sync()
- self.syncRequested.emit()
+ @pyqtSlot()
+ def popupOpened(self) -> None:
+ self._setManualSyncEnabled(True)
+ self._sync_state = SyncState.IDLE
+ self.syncStateChanged.emit(self._sync_state)
@pyqtSlot()
def logout(self) -> None:
diff --git a/cura/API/ConnectionStatus.py b/cura/API/ConnectionStatus.py
new file mode 100644
index 0000000000..332e519ca9
--- /dev/null
+++ b/cura/API/ConnectionStatus.py
@@ -0,0 +1,64 @@
+from typing import Optional
+
+from PyQt5.QtCore import QObject, pyqtSignal, QTimer, pyqtProperty
+from PyQt5.QtNetwork import QNetworkReply
+
+from UM.TaskManagement.HttpRequestManager import HttpRequestManager
+from cura.UltimakerCloud import UltimakerCloudAuthentication
+
+
+class ConnectionStatus(QObject):
+ """Status info for some web services"""
+
+ UPDATE_INTERVAL = 10.0 # seconds
+ ULTIMAKER_CLOUD_STATUS_URL = UltimakerCloudAuthentication.CuraCloudAPIRoot + "/connect/v1/"
+
+ __instance = None # type: Optional[ConnectionStatus]
+
+ internetReachableChanged = pyqtSignal()
+ umCloudReachableChanged = pyqtSignal()
+
+ @classmethod
+ def getInstance(cls, *args, **kwargs) -> "ConnectionStatus":
+ if cls.__instance is None:
+ cls.__instance = cls(*args, **kwargs)
+ return cls.__instance
+
+ def __init__(self, parent: Optional["QObject"] = None):
+ super().__init__(parent)
+
+ self._http = HttpRequestManager.getInstance()
+ self._statuses = {
+ self.ULTIMAKER_CLOUD_STATUS_URL: True,
+ "http://example.com": True
+ }
+
+ # Create a timer for automatic updates
+ self._update_timer = QTimer()
+ self._update_timer.setInterval(int(self.UPDATE_INTERVAL * 1000))
+ # The timer is restarted automatically
+ self._update_timer.setSingleShot(False)
+ self._update_timer.timeout.connect(self._update)
+ self._update_timer.start()
+
+ @pyqtProperty(bool, notify=internetReachableChanged)
+ def isInternetReachable(self) -> bool:
+ # Is any of the test urls reachable?
+ return any(self._statuses.values())
+
+ def _update(self):
+ for url in self._statuses.keys():
+ self._http.get(
+ url = url,
+ callback = self._statusCallback,
+ error_callback = self._statusCallback,
+ timeout = 5
+ )
+
+ def _statusCallback(self, reply: QNetworkReply, error: QNetworkReply.NetworkError = None):
+ url = reply.request().url().toString()
+ prev_statuses = self._statuses.copy()
+ self._statuses[url] = HttpRequestManager.replyIndicatesSuccess(reply, error)
+
+ if any(self._statuses.values()) != any(prev_statuses.values()):
+ self.internetReachableChanged.emit()
diff --git a/cura/API/__init__.py b/cura/API/__init__.py
index 97d0797430..447be98e4b 100644
--- a/cura/API/__init__.py
+++ b/cura/API/__init__.py
@@ -5,6 +5,7 @@ from typing import Optional, TYPE_CHECKING
from PyQt5.QtCore import QObject, pyqtProperty
from cura.API.Backups import Backups
+from cura.API.ConnectionStatus import ConnectionStatus
from cura.API.Interface import Interface
from cura.API.Account import Account
@@ -14,7 +15,7 @@ if TYPE_CHECKING:
class CuraAPI(QObject):
"""The official Cura API that plug-ins can use to interact with Cura.
-
+
Python does not technically prevent talking to other classes as well, but this API provides a version-safe
interface with proper deprecation warnings etc. Usage of any other methods than the ones provided in this API can
cause plug-ins to be unstable.
@@ -44,6 +45,9 @@ class CuraAPI(QObject):
self._backups = Backups(self._application)
+ self._connectionStatus = ConnectionStatus()
+
+ # Interface API
self._interface = Interface(self._application)
def initialize(self) -> None:
@@ -55,6 +59,10 @@ class CuraAPI(QObject):
return self._account
+ @pyqtProperty(QObject, constant = True)
+ def connectionStatus(self) -> "ConnectionStatus":
+ return self._connectionStatus
+
@property
def backups(self) -> "Backups":
"""Backups API"""
diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py
index fa862f3f9e..26d6591099 100644
--- a/cura/CuraPackageManager.py
+++ b/cura/CuraPackageManager.py
@@ -40,10 +40,10 @@ class CuraPackageManager(PackageManager):
machine_with_qualities = []
for container_id in ids:
for global_stack in global_stacks:
- for extruder_nr, extruder_stack in global_stack.extruders.items():
+ for extruder_nr, extruder_stack in enumerate(global_stack.extruderList):
if container_id in (extruder_stack.material.getId(), extruder_stack.material.getMetaData().get("base_file")):
- machine_with_materials.append((global_stack, extruder_nr, container_id))
+ machine_with_materials.append((global_stack, str(extruder_nr), container_id))
if container_id == extruder_stack.quality.getId():
- machine_with_qualities.append((global_stack, extruder_nr, container_id))
+ machine_with_qualities.append((global_stack, str(extruder_nr), container_id))
return machine_with_materials, machine_with_qualities
diff --git a/cura/Machines/Models/BaseMaterialsModel.py b/cura/Machines/Models/BaseMaterialsModel.py
index 3bc7803e9a..bc903da698 100644
--- a/cura/Machines/Models/BaseMaterialsModel.py
+++ b/cura/Machines/Models/BaseMaterialsModel.py
@@ -156,9 +156,10 @@ class BaseMaterialsModel(ListModel):
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
if not global_stack.hasMaterials:
return # There are no materials for this machine, so nothing to do.
- extruder_stack = global_stack.extruders.get(str(self._extruder_position))
- if not extruder_stack:
+ extruder_list = global_stack.extruderList
+ if self._extruder_position > len(extruder_list):
return
+ extruder_stack = extruder_list[self._extruder_position]
nozzle_name = extruder_stack.variant.getName()
machine_node = ContainerTree.getInstance().machines[global_stack.definition.getId()]
if nozzle_name not in machine_node.variants:
diff --git a/cura/Machines/Models/DiscoveredPrintersModel.py b/cura/Machines/Models/DiscoveredPrintersModel.py
index 176baf47e4..4042d4ae17 100644
--- a/cura/Machines/Models/DiscoveredPrintersModel.py
+++ b/cura/Machines/Models/DiscoveredPrintersModel.py
@@ -128,6 +128,7 @@ class DiscoveredPrintersModel(QObject):
self._discovered_printer_by_ip_dict = dict() # type: Dict[str, DiscoveredPrinter]
self._plugin_for_manual_device = None # type: Optional[OutputDevicePlugin]
+ self._network_plugin_queue = [] # type: List[OutputDevicePlugin]
self._manual_device_address = ""
self._manual_device_request_timeout_in_seconds = 5 # timeout for adding a manual device in seconds
@@ -152,20 +153,25 @@ class DiscoveredPrintersModel(QObject):
all_plugins_dict = self._application.getOutputDeviceManager().getAllOutputDevicePlugins()
- can_add_manual_plugins = [item for item in filter(
+ self._network_plugin_queue = [item for item in filter(
lambda plugin_item: plugin_item.canAddManualDevice(address) in priority_order,
all_plugins_dict.values())]
- if not can_add_manual_plugins:
+ if not self._network_plugin_queue:
Logger.log("d", "Could not find a plugin to accept adding %s manually via address.", address)
return
- plugin = max(can_add_manual_plugins, key = lambda p: priority_order.index(p.canAddManualDevice(address)))
- self._plugin_for_manual_device = plugin
- self._plugin_for_manual_device.addManualDevice(address, callback = self._onManualDeviceRequestFinished)
- self._manual_device_address = address
- self._manual_device_request_timer.start()
- self.hasManualDeviceRequestInProgressChanged.emit()
+ self._attemptToAddManualDevice(address)
+
+ def _attemptToAddManualDevice(self, address: str) -> None:
+ if self._network_plugin_queue:
+ self._plugin_for_manual_device = self._network_plugin_queue.pop()
+ Logger.log("d", "Network plugin %s: attempting to add manual device with address %s.",
+ self._plugin_for_manual_device.getId(), address)
+ self._plugin_for_manual_device.addManualDevice(address, callback=self._onManualDeviceRequestFinished)
+ self._manual_device_address = address
+ self._manual_device_request_timer.start()
+ self.hasManualDeviceRequestInProgressChanged.emit()
@pyqtSlot()
def cancelCurrentManualDeviceRequest(self) -> None:
@@ -180,8 +186,11 @@ class DiscoveredPrintersModel(QObject):
self.manualDeviceRequestFinished.emit(False)
def _onManualRequestTimeout(self) -> None:
- Logger.log("w", "Manual printer [%s] request timed out. Cancel the current request.", self._manual_device_address)
+ address = self._manual_device_address
+ Logger.log("w", "Manual printer [%s] request timed out. Cancel the current request.", address)
self.cancelCurrentManualDeviceRequest()
+ if self._network_plugin_queue:
+ self._attemptToAddManualDevice(address)
hasManualDeviceRequestInProgressChanged = pyqtSignal()
@@ -197,6 +206,8 @@ class DiscoveredPrintersModel(QObject):
self._manual_device_address = ""
self.hasManualDeviceRequestInProgressChanged.emit()
self.manualDeviceRequestFinished.emit(success)
+ if not success and self._network_plugin_queue:
+ self._attemptToAddManualDevice(address)
@pyqtProperty("QVariantMap", notify = discoveredPrintersChanged)
def discoveredPrintersByAddress(self) -> Dict[str, DiscoveredPrinter]:
diff --git a/cura/Machines/Models/GlobalStacksModel.py b/cura/Machines/Models/GlobalStacksModel.py
index 73d58f22c0..6d091659a8 100644
--- a/cura/Machines/Models/GlobalStacksModel.py
+++ b/cura/Machines/Models/GlobalStacksModel.py
@@ -19,6 +19,7 @@ class GlobalStacksModel(ListModel):
ConnectionTypeRole = Qt.UserRole + 4
MetaDataRole = Qt.UserRole + 5
DiscoverySourceRole = Qt.UserRole + 6 # For separating local and remote printers in the machine management page
+ RemovalWarningRole = Qt.UserRole + 7
def __init__(self, parent = None) -> None:
super().__init__(parent)
@@ -66,13 +67,21 @@ class GlobalStacksModel(ListModel):
if parseBool(container_stack.getMetaDataEntry("hidden", False)):
continue
- section_name = "Network enabled printers" if has_remote_connection else "Local printers"
+ device_name = container_stack.getMetaDataEntry("group_name", container_stack.getName())
+ section_name = "Connected printers" if has_remote_connection else "Preset printers"
section_name = self._catalog.i18nc("@info:title", section_name)
- items.append({"name": container_stack.getMetaDataEntry("group_name", container_stack.getName()),
+ default_removal_warning = self._catalog.i18nc(
+ "@label ({} is object name)",
+ "Are you sure you wish to remove {}? This cannot be undone!", device_name
+ )
+ removal_warning = container_stack.getMetaDataEntry("removal_warning", default_removal_warning)
+
+ items.append({"name": device_name,
"id": container_stack.getId(),
"hasRemoteConnection": has_remote_connection,
"metadata": container_stack.getMetaData().copy(),
- "discoverySource": section_name})
- items.sort(key = lambda i: (not i["hasRemoteConnection"], i["name"]))
+ "discoverySource": section_name,
+ "removalWarning": removal_warning})
+ items.sort(key=lambda i: (not i["hasRemoteConnection"], i["name"]))
self.setItems(items)
diff --git a/cura/Machines/Models/QualitySettingsModel.py b/cura/Machines/Models/QualitySettingsModel.py
index 04af5aff7b..20d8fd20ff 100644
--- a/cura/Machines/Models/QualitySettingsModel.py
+++ b/cura/Machines/Models/QualitySettingsModel.py
@@ -151,7 +151,7 @@ class QualitySettingsModel(ListModel):
if self._selected_position == self.GLOBAL_STACK_POSITION:
user_value = global_container_stack.userChanges.getProperty(definition.key, "value")
else:
- extruder_stack = global_container_stack.extruders[str(self._selected_position)]
+ extruder_stack = global_container_stack.extruderList[self._selected_position]
user_value = extruder_stack.userChanges.getProperty(definition.key, "value")
if profile_value is None and user_value is None:
diff --git a/cura/Scene/ConvexHullDecorator.py b/cura/Scene/ConvexHullDecorator.py
index 3dee409761..df496aa401 100644
--- a/cura/Scene/ConvexHullDecorator.py
+++ b/cura/Scene/ConvexHullDecorator.py
@@ -241,7 +241,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
if self._node is None:
return None
if self._node.callDecoration("isGroup"):
- points = numpy.zeros((0, 2), dtype=numpy.int32)
+ points = numpy.zeros((0, 2), dtype = numpy.int32)
for child in self._node.getChildren():
child_hull = child.callDecoration("_compute2DConvexHull")
if child_hull:
@@ -285,7 +285,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
# Do not throw away vertices: the convex hull may be too small and objects can collide.
# vertex_data = vertex_data[vertex_data[:,1] >= -0.01]
- if len(vertex_data) >= 4: # type: ignore # mypy and numpy don't play along well just yet.
+ if vertex_data is not None and len(vertex_data) >= 4: # type: ignore # mypy and numpy don't play along well just yet.
# Round the vertex data to 1/10th of a mm, then remove all duplicate vertices
# This is done to greatly speed up further convex hull calculations as the convex hull
# becomes much less complex when dealing with highly detailed models.
diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py
index 1ada51af59..a0cfd61e49 100644
--- a/cura/Settings/CuraContainerRegistry.py
+++ b/cura/Settings/CuraContainerRegistry.py
@@ -47,9 +47,9 @@ class CuraContainerRegistry(ContainerRegistry):
@override(ContainerRegistry)
def addContainer(self, container: ContainerInterface) -> None:
"""Overridden from ContainerRegistry
-
+
Adds a container to the registry.
-
+
This will also try to convert a ContainerStack to either Extruder or
Global stack based on metadata information.
"""
@@ -70,7 +70,7 @@ class CuraContainerRegistry(ContainerRegistry):
def createUniqueName(self, container_type: str, current_name: str, new_name: str, fallback_name: str) -> str:
"""Create a name that is not empty and unique
-
+
:param container_type: :type{string} Type of the container (machine, quality, ...)
:param current_name: :type{} Current name of the container, which may be an acceptable option
:param new_name: :type{string} Base name, which may not be unique
@@ -95,7 +95,7 @@ class CuraContainerRegistry(ContainerRegistry):
def _containerExists(self, container_type: str, container_name: str):
"""Check if a container with of a certain type and a certain name or id exists
-
+
Both the id and the name are checked, because they may not be the same and it is better if they are both unique
:param container_type: :type{string} Type of the container (machine, quality, ...)
:param container_name: :type{string} Name to check
@@ -107,7 +107,7 @@ class CuraContainerRegistry(ContainerRegistry):
def exportQualityProfile(self, container_list: List[InstanceContainer], file_name: str, file_type: str) -> bool:
"""Exports an profile to a file
-
+
:param container_list: :type{list} the containers to export. This is not
necessarily in any order!
:param file_name: :type{str} the full path and filename to export to.
@@ -160,7 +160,7 @@ class CuraContainerRegistry(ContainerRegistry):
def _findProfileWriter(self, extension: str, description: str) -> Optional[ProfileWriter]:
"""Gets the plugin object matching the criteria
-
+
:param extension:
:param description:
:return: The plugin object matching the given extension and description.
@@ -177,7 +177,7 @@ class CuraContainerRegistry(ContainerRegistry):
def importProfile(self, file_name: str) -> Dict[str, str]:
"""Imports a profile from a file
-
+
:param file_name: The full path and filename of the profile to import.
:return: Dict with a 'status' key containing the string 'ok' or 'error',
and a 'message' key containing a message for the user.
@@ -192,9 +192,7 @@ class CuraContainerRegistry(ContainerRegistry):
return {"status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename>!", "Can't import profile from <filename>{0}</filename> before a printer is added.", file_name)}
container_tree = ContainerTree.getInstance()
- machine_extruders = []
- for position in sorted(global_stack.extruders):
- machine_extruders.append(global_stack.extruders[position])
+ machine_extruders = global_stack.extruderList
plugin_registry = PluginRegistry.getInstance()
extension = file_name.split(".")[-1]
@@ -275,7 +273,7 @@ class CuraContainerRegistry(ContainerRegistry):
if len(profile_or_list) == 1:
global_profile = profile_or_list[0]
extruder_profiles = []
- for idx, extruder in enumerate(global_stack.extruders.values()):
+ for idx, extruder in enumerate(global_stack.extruderList):
profile_id = ContainerRegistry.getInstance().uniqueName(global_stack.getId() + "_extruder_" + str(idx + 1))
profile = InstanceContainer(profile_id)
profile.setName(quality_name)
@@ -353,7 +351,7 @@ class CuraContainerRegistry(ContainerRegistry):
@override(ContainerRegistry)
def _isMetadataValid(self, metadata: Optional[Dict[str, Any]]) -> bool:
"""Check if the metadata for a container is okay before adding it.
-
+
This overrides the one from UM.Settings.ContainerRegistry because we
also require that the setting_version is correct.
"""
@@ -371,11 +369,11 @@ class CuraContainerRegistry(ContainerRegistry):
def _configureProfile(self, profile: InstanceContainer, id_seed: str, new_name: str, machine_definition_id: str) -> Optional[str]:
"""Update an imported profile to match the current machine configuration.
-
+
:param profile: The profile to configure.
:param id_seed: The base ID for the profile. May be changed so it does not conflict with existing containers.
:param new_name: The new name for the profile.
-
+
:return: None if configuring was successful or an error message if an error occurred.
"""
@@ -438,7 +436,7 @@ class CuraContainerRegistry(ContainerRegistry):
def _getIOPlugins(self, io_type):
"""Gets a list of profile writer plugins
-
+
:return: List of tuples of (plugin_id, meta_data).
"""
plugin_registry = PluginRegistry.getInstance()
diff --git a/cura/Settings/CuraStackBuilder.py b/cura/Settings/CuraStackBuilder.py
index 5dc32f6e24..37469f95f7 100644
--- a/cura/Settings/CuraStackBuilder.py
+++ b/cura/Settings/CuraStackBuilder.py
@@ -65,7 +65,7 @@ class CuraStackBuilder:
except IndexError:
return None
- for new_extruder in new_global_stack.extruders.values(): # Only register the extruders if we're sure that all of them are correct.
+ for new_extruder in new_global_stack.extruderList: # Only register the extruders if we're sure that all of them are correct.
registry.addContainer(new_extruder)
# Register the global stack after the extruder stacks are created. This prevents the registry from adding another
diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py
index f9ffde4872..2dc01425fc 100755
--- a/cura/Settings/ExtruderManager.py
+++ b/cura/Settings/ExtruderManager.py
@@ -21,7 +21,7 @@ if TYPE_CHECKING:
class ExtruderManager(QObject):
"""Manages all existing extruder stacks.
-
+
This keeps a list of extruder stacks for each machine.
"""
@@ -54,10 +54,10 @@ class ExtruderManager(QObject):
@pyqtProperty(str, notify = activeExtruderChanged)
def activeExtruderStackId(self) -> Optional[str]:
"""Gets the unique identifier of the currently active extruder stack.
-
+
The currently active extruder stack is the stack that is currently being
edited.
-
+
:return: The unique ID of the currently active extruder stack.
"""
@@ -83,7 +83,7 @@ class ExtruderManager(QObject):
@pyqtSlot(int)
def setActiveExtruderIndex(self, index: int) -> None:
"""Changes the active extruder by index.
-
+
:param index: The index of the new active extruder.
"""
@@ -132,7 +132,7 @@ class ExtruderManager(QObject):
def resetSelectedObjectExtruders(self) -> None:
"""Reset the internal list used for the selectedObjectExtruders property
-
+
This will trigger a recalculation of the extruders used for the
selection.
"""
@@ -154,28 +154,9 @@ class ExtruderManager(QObject):
return self._extruder_trains[global_container_stack.getId()][str(index)]
return None
- def registerExtruder(self, extruder_train: "ExtruderStack", machine_id: str) -> None:
- changed = False
-
- if machine_id not in self._extruder_trains:
- self._extruder_trains[machine_id] = {}
- changed = True
-
- # do not register if an extruder has already been registered at the position on this machine
- if any(item.getId() == extruder_train.getId() for item in self._extruder_trains[machine_id].values()):
- Logger.log("w", "Extruder [%s] has already been registered on machine [%s], not doing anything",
- extruder_train.getId(), machine_id)
- return
-
- if extruder_train:
- self._extruder_trains[machine_id][extruder_train.getMetaDataEntry("position")] = extruder_train
- changed = True
- if changed:
- self.extrudersChanged.emit(machine_id)
-
def getAllExtruderSettings(self, setting_key: str, prop: str) -> List[Any]:
"""Gets a property of a setting for all extruders.
-
+
:param setting_key: :type{str} The setting to get the property of.
:param prop: :type{str} The property to get.
:return: :type{List} the list of results
@@ -197,14 +178,14 @@ class ExtruderManager(QObject):
def getUsedExtruderStacks(self) -> List["ExtruderStack"]:
"""Gets the extruder stacks that are actually being used at the moment.
-
+
An extruder stack is being used if it is the extruder to print any mesh
with, or if it is the support infill extruder, the support interface
extruder, or the bed adhesion extruder.
-
+
If there are no extruders, this returns the global stack as a singleton
list.
-
+
:return: A list of extruder stacks.
"""
@@ -294,7 +275,7 @@ class ExtruderManager(QObject):
def getInitialExtruderNr(self) -> int:
"""Get the extruder that the print will start with.
-
+
This should mirror the implementation in CuraEngine of
``FffGcodeWriter::getStartExtruder()``.
"""
@@ -315,7 +296,7 @@ class ExtruderManager(QObject):
def removeMachineExtruders(self, machine_id: str) -> None:
"""Removes the container stack and user profile for the extruders for a specific machine.
-
+
:param machine_id: The machine to remove the extruders for.
"""
@@ -327,7 +308,7 @@ class ExtruderManager(QObject):
def getMachineExtruders(self, machine_id: str) -> List["ExtruderStack"]:
"""Returns extruders for a specific machine.
-
+
:param machine_id: The machine to get the extruders of.
"""
@@ -337,7 +318,7 @@ class ExtruderManager(QObject):
def getActiveExtruderStacks(self) -> List["ExtruderStack"]:
"""Returns the list of active extruder stacks, taking into account the machine extruder count.
-
+
:return: :type{List[ContainerStack]} a list of
"""
@@ -423,11 +404,11 @@ class ExtruderManager(QObject):
@pyqtSlot(str, result="QVariant")
def getInstanceExtruderValues(self, key: str) -> List:
"""Get all extruder values for a certain setting.
-
+
This is exposed to qml for display purposes
-
+
:param key: The key of the setting to retrieve values for.
-
+
:return: String representing the extruder values
"""
@@ -436,11 +417,11 @@ class ExtruderManager(QObject):
@staticmethod
def getResolveOrValue(key: str) -> Any:
"""Get the resolve value or value for a given key
-
+
This is the effective value for a given key, it is used for values in the global stack.
This is exposed to SettingFunction to use in value functions.
:param key: The key of the setting to get the value of.
-
+
:return: The effective value
"""
diff --git a/cura/Settings/ExtruderStack.py b/cura/Settings/ExtruderStack.py
index 7369838baa..52dfffd3a5 100644
--- a/cura/Settings/ExtruderStack.py
+++ b/cura/Settings/ExtruderStack.py
@@ -45,9 +45,6 @@ class ExtruderStack(CuraContainerStack):
stack.addExtruder(self)
self.setMetaDataEntry("machine", stack.id)
- # For backward compatibility: Register the extruder with the Extruder Manager
- ExtruderManager.getInstance().registerExtruder(self, stack.id)
-
@override(ContainerStack)
def getNextStack(self) -> Optional["GlobalStack"]:
return super().getNextStack()
diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py
index e56a3e38f3..045307c673 100755
--- a/cura/Settings/MachineManager.py
+++ b/cura/Settings/MachineManager.py
@@ -499,6 +499,10 @@ class MachineManager(QObject):
def activeMachineHasCloudConnection(self) -> bool:
# A cloud connection is only available if any output device actually is a cloud connected device.
return any(d.connectionType == ConnectionType.CloudConnection for d in self._printer_output_devices)
+
+ @pyqtProperty(bool, notify = printerConnectedStatusChanged)
+ def activeMachineHasCloudRegistration(self) -> bool:
+ return self.activeMachine is not None and ConnectionType.CloudConnection in self.activeMachine.configuredConnectionTypes
@pyqtProperty(bool, notify = printerConnectedStatusChanged)
def activeMachineIsUsingCloudConnection(self) -> bool:
@@ -943,7 +947,7 @@ class MachineManager(QObject):
@pyqtSlot(int, bool)
def setExtruderEnabled(self, position: int, enabled: bool) -> None:
- if self._global_container_stack is None or str(position) not in self._global_container_stack.extruders:
+ if self._global_container_stack is None or position >= len(self._global_container_stack.extruderList):
Logger.log("w", "Could not find extruder on position %s.", position)
return
extruder = self._global_container_stack.extruderList[position]
@@ -1106,14 +1110,14 @@ class MachineManager(QObject):
self._global_container_stack.quality = quality_container
self._global_container_stack.qualityChanges = quality_changes_container
- for position, extruder in self._global_container_stack.extruders.items():
+ for position, extruder in enumerate(self._global_container_stack.extruderList):
quality_node = None
if quality_group is not None:
- quality_node = quality_group.nodes_for_extruders.get(int(position))
+ quality_node = quality_group.nodes_for_extruders.get(position)
quality_changes_container = empty_quality_changes_container
quality_container = empty_quality_container
- quality_changes_metadata = quality_changes_group.metadata_per_extruder.get(int(position))
+ quality_changes_metadata = quality_changes_group.metadata_per_extruder.get(position)
if quality_changes_metadata:
containers = container_registry.findContainers(id = quality_changes_metadata["id"])
if containers:
@@ -1132,7 +1136,7 @@ class MachineManager(QObject):
def _setVariantNode(self, position: str, variant_node: "VariantNode") -> None:
if self._global_container_stack is None:
return
- self._global_container_stack.extruders[position].variant = variant_node.container
+ self._global_container_stack.extruderList[int(position)].variant = variant_node.container
self.activeVariantChanged.emit()
def _setGlobalVariant(self, container_node: "ContainerNode") -> None:
@@ -1147,7 +1151,7 @@ class MachineManager(QObject):
return
if material_node and material_node.container:
material_container = material_node.container
- self._global_container_stack.extruders[position].material = material_container
+ self._global_container_stack.extruderList[int(position)].material = material_container
root_material_id = material_container.getMetaDataEntry("base_file", None)
else:
self._global_container_stack.extruderList[int(position)].material = empty_material_container
@@ -1251,7 +1255,7 @@ class MachineManager(QObject):
if self._global_container_stack is None:
return
if position is None:
- position_list = list(self._global_container_stack.extruders.keys())
+ position_list = [str(position) for position in range(len(self._global_container_stack.extruderList))]
else:
position_list = [position]
@@ -1325,17 +1329,15 @@ class MachineManager(QObject):
# Keep a temporary copy of the global and per-extruder user changes and transfer them to the user changes
# of the new machine after the new_machine becomes active.
global_user_changes = self._global_container_stack.userChanges
- per_extruder_user_changes = {}
- for extruder_name, extruder_stack in self._global_container_stack.extruders.items():
- per_extruder_user_changes[extruder_name] = extruder_stack.userChanges
+ per_extruder_user_changes = [extruder_stack.userChanges for extruder_stack in self._global_container_stack.extruderList]
self.setActiveMachine(new_machine.getId())
# Apply the global and per-extruder userChanges to the new_machine (which is of different type than the
# previous one).
self._global_container_stack.setUserChanges(global_user_changes)
- for extruder_name in self._global_container_stack.extruders.keys():
- self._global_container_stack.extruders[extruder_name].setUserChanges(per_extruder_user_changes[extruder_name])
+ for i, user_changes in enumerate(per_extruder_user_changes):
+ self._global_container_stack.extruderList[i].setUserChanges(per_extruder_user_changes[i])
@pyqtSlot(QObject)
def applyRemoteConfiguration(self, configuration: PrinterConfigurationModel) -> None:
@@ -1393,7 +1395,7 @@ class MachineManager(QObject):
material_container_node = variant_node.materials.get(base_file, material_container_node)
self._setMaterial(position, material_container_node)
- self._global_container_stack.extruders[position].setEnabled(True)
+ self._global_container_stack.extruderList[int(position)].setEnabled(True)
self.updateMaterialWithVariant(position)
self.updateDefaultExtruder()
@@ -1446,7 +1448,7 @@ class MachineManager(QObject):
if you update an active machine, special measures have to be taken.
"""
if global_stack is not None and global_stack != self._global_container_stack:
- global_stack.extruders[position].material = container_node.container
+ global_stack.extruderList[int(position)].material = container_node.container
return
position = str(position)
self.blurSettings.emit()
@@ -1639,7 +1641,7 @@ class MachineManager(QObject):
return
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
self._setQualityGroup(self.activeQualityGroup())
- for stack in [self._global_container_stack] + list(self._global_container_stack.extruders.values()):
+ for stack in [self._global_container_stack] + self._global_container_stack.extruderList:
stack.userChanges.clear()
@pyqtProperty(QObject, fset = setQualityChangesGroup, notify = activeQualityChangesGroupChanged)