Merge branch 'main' into patch-1

This commit is contained in:
GroovyDrifter 2022-09-01 19:32:14 +02:00 committed by GitHub
commit 19146fad3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
278 changed files with 1028 additions and 815 deletions

View File

@ -53,6 +53,7 @@ env:
jobs: jobs:
conan-package-create: conan-package-create:
if: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
runs-on: ${{ inputs.runs_on }} runs-on: ${{ inputs.runs_on }}
steps: steps:

View File

@ -53,9 +53,18 @@ jobs:
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v3 uses: actions/checkout@v3
if: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
with: with:
ref: ${{ github.head_ref }}
fetch-depth: 0 fetch-depth: 0
ref: ${{ github.head_ref }}
- name: Checkout repo PR
uses: actions/checkout@v3
if: ${{ github.event.pull_request.head.repo.full_name != github.repository }}
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
- name: Setup Python and pip - name: Setup Python and pip
uses: actions/setup-python@v4 uses: actions/setup-python@v4
@ -106,7 +115,7 @@ jobs:
else: else:
channel = repo.active_branch.name.split("_")[0].replace("-", "_").lower() channel = repo.active_branch.name.split("_")[0].replace("-", "_").lower()
if event_name == "pull_request": if "pull_request" in event_name:
channel = f"pr_{issue_number}" channel = f"pr_{issue_number}"
# %% Get the actual version # %% Get the actual version
@ -121,6 +130,7 @@ jobs:
latest_branch_version = version latest_branch_version = version
latest_branch_tag = repo.tag(tag) latest_branch_tag = repo.tag(tag)
if latest_branch_tag:
# %% Get the actual version # %% Get the actual version
no_commits = 0 no_commits = 0
for commit in repo.iter_commits("HEAD"): for commit in repo.iter_commits("HEAD"):
@ -151,6 +161,9 @@ jobs:
actual_version = f"{latest_branch_version.major}.{bump_up_minor}.{latest_branch_version.patch}-alpha+{buildmetadata}{channel_metadata}" actual_version = f"{latest_branch_version.major}.{bump_up_minor}.{latest_branch_version.patch}-alpha+{buildmetadata}{channel_metadata}"
else: else:
actual_version = f"{latest_branch_version.major}.{latest_branch_version.minor}.{latest_branch_version.patch}-{latest_branch_version.prerelease.lower()}+{buildmetadata}{channel_metadata}" actual_version = f"{latest_branch_version.major}.{latest_branch_version.minor}.{latest_branch_version.patch}-{latest_branch_version.prerelease.lower()}+{buildmetadata}{channel_metadata}"
else:
# FIXME: for external PR's
actual_version = f"5.2.0-alpha+{buildmetadata}pr_{issue_number}"
# %% print to output # %% print to output
cmd_name = ["echo", f"::set-output name=name::{project_name}"] cmd_name = ["echo", f"::set-output name=name::{project_name}"]

View File

@ -146,8 +146,6 @@ class CuraApplication(QtApplication):
DefinitionChangesContainer = Resources.UserType + 10 DefinitionChangesContainer = Resources.UserType + 10
SettingVisibilityPreset = Resources.UserType + 11 SettingVisibilityPreset = Resources.UserType + 11
IntentInstanceContainer = Resources.UserType + 12 IntentInstanceContainer = Resources.UserType + 12
AbstractMachineStack = Resources.UserType + 13
pyqtEnum(ResourceTypes) pyqtEnum(ResourceTypes)
@ -426,7 +424,6 @@ class CuraApplication(QtApplication):
Resources.addStorageType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes") Resources.addStorageType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes")
Resources.addStorageType(self.ResourceTypes.SettingVisibilityPreset, "setting_visibility") Resources.addStorageType(self.ResourceTypes.SettingVisibilityPreset, "setting_visibility")
Resources.addStorageType(self.ResourceTypes.IntentInstanceContainer, "intent") Resources.addStorageType(self.ResourceTypes.IntentInstanceContainer, "intent")
Resources.addStorageType(self.ResourceTypes.AbstractMachineStack, "abstract_machine_instances")
self._container_registry.addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality") self._container_registry.addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality")
self._container_registry.addResourceType(self.ResourceTypes.QualityChangesInstanceContainer, "quality_changes") self._container_registry.addResourceType(self.ResourceTypes.QualityChangesInstanceContainer, "quality_changes")
@ -437,7 +434,6 @@ class CuraApplication(QtApplication):
self._container_registry.addResourceType(self.ResourceTypes.MachineStack, "machine") self._container_registry.addResourceType(self.ResourceTypes.MachineStack, "machine")
self._container_registry.addResourceType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes") self._container_registry.addResourceType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes")
self._container_registry.addResourceType(self.ResourceTypes.IntentInstanceContainer, "intent") self._container_registry.addResourceType(self.ResourceTypes.IntentInstanceContainer, "intent")
self._container_registry.addResourceType(self.ResourceTypes.AbstractMachineStack, "abstract_machine")
Resources.addType(self.ResourceTypes.QmlFiles, "qml") Resources.addType(self.ResourceTypes.QmlFiles, "qml")
Resources.addType(self.ResourceTypes.Firmware, "firmware") Resources.addType(self.ResourceTypes.Firmware, "firmware")
@ -486,7 +482,6 @@ class CuraApplication(QtApplication):
("variant", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.VariantInstanceContainer, "application/x-uranium-instancecontainer"), ("variant", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.VariantInstanceContainer, "application/x-uranium-instancecontainer"),
("setting_visibility", SettingVisibilityPresetsModel.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.SettingVisibilityPreset, "application/x-uranium-preferences"), ("setting_visibility", SettingVisibilityPresetsModel.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.SettingVisibilityPreset, "application/x-uranium-preferences"),
("machine", 2): (Resources.DefinitionContainers, "application/x-uranium-definitioncontainer"), ("machine", 2): (Resources.DefinitionContainers, "application/x-uranium-definitioncontainer"),
("abstract_machine", 1): (Resources.DefinitionContainers, "application/x-uranium-definitioncontainer"),
("extruder", 2): (Resources.DefinitionContainers, "application/x-uranium-definitioncontainer") ("extruder", 2): (Resources.DefinitionContainers, "application/x-uranium-definitioncontainer")
} }
) )

View File

@ -94,7 +94,7 @@ class MachineAction(QObject, PluginObject):
plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId()) plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
if plugin_path is None: if plugin_path is None:
Logger.log("e", "Cannot create QML view: cannot find plugin path for plugin [%s]", self.getPluginId()) Logger.error(f"Cannot create QML view: cannot find plugin path for plugin {self.getPluginId()}")
return None return None
path = os.path.join(plugin_path, self._qml_url) path = os.path.join(plugin_path, self._qml_url)
@ -106,7 +106,7 @@ class MachineAction(QObject, PluginObject):
def qmlPath(self) -> "QUrl": def qmlPath(self) -> "QUrl":
plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId()) plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
if plugin_path is None: if plugin_path is None:
Logger.log("e", "Cannot create QML view: cannot find plugin path for plugin [%s]", self.getPluginId()) Logger.error(f"Cannot create QML view: cannot find plugin path for plugin {self.getPluginId()}")
return QUrl("") return QUrl("")
path = os.path.join(plugin_path, self._qml_url) path = os.path.join(plugin_path, self._qml_url)
return QUrl.fromLocalFile(path) return QUrl.fromLocalFile(path)

View File

@ -1,14 +1,18 @@
# Copyright (c) 2022 Ultimaker B.V. # Copyright (c) 2022 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from PyQt6.QtCore import Qt, QTimer # The MachineListModel is used to display the connected printers in the interface. Both the abstract machines and all
# online cloud connected printers are represented within this ListModel. Additional information such as the number of
# connected printers for each printer type is gathered.
from PyQt6.QtCore import Qt, QTimer, pyqtSlot, pyqtProperty, pyqtSignal
from UM.Qt.ListModel import ListModel from UM.Qt.ListModel import ListModel
from UM.Settings.ContainerStack import ContainerStack from UM.Settings.ContainerStack import ContainerStack
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
from UM.Util import parseBool from UM.Util import parseBool
from cura.PrinterOutput.PrinterOutputDevice import ConnectionType
from cura.Settings.AbstractMachine import AbstractMachine
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
from cura.Settings.GlobalStack import GlobalStack from cura.Settings.GlobalStack import GlobalStack
@ -19,12 +23,15 @@ class MachineListModel(ListModel):
HasRemoteConnectionRole = Qt.ItemDataRole.UserRole + 3 HasRemoteConnectionRole = Qt.ItemDataRole.UserRole + 3
MetaDataRole = Qt.ItemDataRole.UserRole + 4 MetaDataRole = Qt.ItemDataRole.UserRole + 4
IsOnlineRole = Qt.ItemDataRole.UserRole + 5 IsOnlineRole = Qt.ItemDataRole.UserRole + 5
MachineTypeRole = Qt.ItemDataRole.UserRole + 6 MachineCountRole = Qt.ItemDataRole.UserRole + 6
MachineCountRole = Qt.ItemDataRole.UserRole + 7 IsAbstractMachineRole = Qt.ItemDataRole.UserRole + 7
ComponentTypeRole = Qt.ItemDataRole.UserRole + 8
def __init__(self, parent=None) -> None: def __init__(self, parent=None) -> None:
super().__init__(parent) super().__init__(parent)
self._show_cloud_printers = False
self._catalog = i18nCatalog("cura") self._catalog = i18nCatalog("cura")
self.addRoleName(self.NameRole, "name") self.addRoleName(self.NameRole, "name")
@ -32,8 +39,9 @@ class MachineListModel(ListModel):
self.addRoleName(self.HasRemoteConnectionRole, "hasRemoteConnection") self.addRoleName(self.HasRemoteConnectionRole, "hasRemoteConnection")
self.addRoleName(self.MetaDataRole, "metadata") self.addRoleName(self.MetaDataRole, "metadata")
self.addRoleName(self.IsOnlineRole, "isOnline") self.addRoleName(self.IsOnlineRole, "isOnline")
self.addRoleName(self.MachineTypeRole, "machineType")
self.addRoleName(self.MachineCountRole, "machineCount") self.addRoleName(self.MachineCountRole, "machineCount")
self.addRoleName(self.IsAbstractMachineRole, "isAbstractMachine")
self.addRoleName(self.ComponentTypeRole, "componentType")
self._change_timer = QTimer() self._change_timer = QTimer()
self._change_timer.setInterval(200) self._change_timer.setInterval(200)
@ -46,6 +54,18 @@ class MachineListModel(ListModel):
CuraContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChanged) CuraContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChanged)
self._updateDelayed() self._updateDelayed()
showCloudPrintersChanged = pyqtSignal(bool)
@pyqtProperty(bool, notify=showCloudPrintersChanged)
def showCloudPrinters(self) -> bool:
return self._show_cloud_printers
@pyqtSlot(bool)
def setShowCloudPrinters(self, show_cloud_printers: bool) -> None:
self._show_cloud_printers = show_cloud_printers
self._updateDelayed()
self.showCloudPrintersChanged.emit(show_cloud_printers)
def _onContainerChanged(self, container) -> None: def _onContainerChanged(self, container) -> None:
"""Handler for container added/removed events from registry""" """Handler for container added/removed events from registry"""
@ -57,25 +77,46 @@ class MachineListModel(ListModel):
self._change_timer.start() self._change_timer.start()
def _update(self) -> None: def _update(self) -> None:
self.setItems([]) # Clear items self.clear()
other_machine_stacks = CuraContainerRegistry.getInstance().findContainerStacks(type="machine") other_machine_stacks = CuraContainerRegistry.getInstance().findContainerStacks(type="machine")
abstract_machine_stacks = CuraContainerRegistry.getInstance().findContainerStacks(type = "abstract_machine") abstract_machine_stacks = CuraContainerRegistry.getInstance().findContainerStacks(is_abstract_machine = "True")
abstract_machine_stacks.sort(key = lambda machine: machine.getName(), reverse = True) abstract_machine_stacks.sort(key = lambda machine: machine.getName(), reverse = True)
for abstract_machine in abstract_machine_stacks: for abstract_machine in abstract_machine_stacks:
online_machine_stacks = AbstractMachine.getMachines(abstract_machine, online_only = True) definition_id = abstract_machine.definition.getId()
from cura.CuraApplication import CuraApplication
machines_manager = CuraApplication.getInstance().getMachineManager()
online_machine_stacks = machines_manager.getMachinesWithDefinition(definition_id, online_only = True)
# Create a list item for abstract machine # Create a list item for abstract machine
self.addItem(abstract_machine, len(online_machine_stacks)) self.addItem(abstract_machine, len(online_machine_stacks))
other_machine_stacks.remove(abstract_machine)
if abstract_machine in online_machine_stacks:
online_machine_stacks.remove(abstract_machine)
# Create list of machines that are children of the abstract machine # Create list of machines that are children of the abstract machine
for stack in online_machine_stacks: for stack in online_machine_stacks:
if self._show_cloud_printers:
self.addItem(stack) self.addItem(stack)
# Remove this machine from the other stack list # Remove this machine from the other stack list
if stack in other_machine_stacks:
other_machine_stacks.remove(stack) other_machine_stacks.remove(stack)
if len(abstract_machine_stacks) > 0:
if self._show_cloud_printers:
self.appendItem({"componentType": "HIDE_BUTTON",
"isOnline": True,
"isAbstractMachine": False,
"machineCount": 0
})
else:
self.appendItem({"componentType": "SHOW_BUTTON",
"isOnline": True,
"isAbstractMachine": False,
"machineCount": 0
})
for stack in other_machine_stacks: for stack in other_machine_stacks:
self.addItem(stack) self.addItem(stack)
@ -83,10 +124,19 @@ class MachineListModel(ListModel):
if parseBool(container_stack.getMetaDataEntry("hidden", False)): if parseBool(container_stack.getMetaDataEntry("hidden", False)):
return return
self.appendItem({"name": container_stack.getName(), # This is required because machines loaded from projects have the is_online="True" but no connection type.
# We want to display them the same way as unconnected printers in this case.
has_connection = False
has_connection |= parseBool(container_stack.getMetaDataEntry("is_abstract_machine", False))
for connection_type in [ConnectionType.NetworkConnection.value, ConnectionType.CloudConnection.value]:
has_connection |= connection_type in container_stack.configuredConnectionTypes
self.appendItem({
"componentType": "MACHINE",
"name": container_stack.getName(),
"id": container_stack.getId(), "id": container_stack.getId(),
"metadata": container_stack.getMetaData().copy(), "metadata": container_stack.getMetaData().copy(),
"isOnline": parseBool(container_stack.getMetaDataEntry("is_online", False)), "isOnline": parseBool(container_stack.getMetaDataEntry("is_online", False)) and has_connection,
"machineType": container_stack.getMetaDataEntry("type"), "isAbstractMachine": parseBool(container_stack.getMetaDataEntry("is_abstract_machine", False)),
"machineCount": machine_count, "machineCount": machine_count,
}) })

View File

@ -350,5 +350,6 @@ class PrinterOutputModel(QObject):
self.availableConfigurationsChanged.emit() self.availableConfigurationsChanged.emit()
def setAvailableConfigurations(self, new_configurations: List[PrinterConfigurationModel]) -> None: def setAvailableConfigurations(self, new_configurations: List[PrinterConfigurationModel]) -> None:
if self._available_printer_configurations != new_configurations:
self._available_printer_configurations = new_configurations self._available_printer_configurations = new_configurations
self.availableConfigurationsChanged.emit() self.availableConfigurationsChanged.emit()

View File

@ -56,7 +56,6 @@ class PrinterOutputDevice(QObject, OutputDevice):
For all other uses it should be used in the same way as a "regular" OutputDevice. For all other uses it should be used in the same way as a "regular" OutputDevice.
""" """
printersChanged = pyqtSignal() printersChanged = pyqtSignal()
connectionStateChanged = pyqtSignal(str) connectionStateChanged = pyqtSignal(str)
acceptsCommandsChanged = pyqtSignal() acceptsCommandsChanged = pyqtSignal()
@ -183,8 +182,8 @@ class PrinterOutputDevice(QObject, OutputDevice):
@pyqtProperty(QObject, constant = True) @pyqtProperty(QObject, constant = True)
def monitorItem(self) -> QObject: def monitorItem(self) -> QObject:
# Note that we specifically only check if the monitor component is created. # Note that we specifically only check if the monitor component is created.
# It could be that it failed to actually create the qml item! If we check if the item was created, it will try to # It could be that it failed to actually create the qml item! If we check if the item was created, it will try
# create the item (and fail) every time. # to create the item (and fail) every time.
if not self._monitor_component: if not self._monitor_component:
self._createMonitorViewFromQML() self._createMonitorViewFromQML()
return self._monitor_item return self._monitor_item
@ -237,9 +236,9 @@ class PrinterOutputDevice(QObject, OutputDevice):
self.acceptsCommandsChanged.emit() self.acceptsCommandsChanged.emit()
# Returns the unique configurations of the printers within this output device
@pyqtProperty("QVariantList", notify = uniqueConfigurationsChanged) @pyqtProperty("QVariantList", notify = uniqueConfigurationsChanged)
def uniqueConfigurations(self) -> List["PrinterConfigurationModel"]: def uniqueConfigurations(self) -> List["PrinterConfigurationModel"]:
""" Returns the unique configurations of the printers within this output device """
return self._unique_configurations return self._unique_configurations
def _updateUniqueConfigurations(self) -> None: def _updateUniqueConfigurations(self) -> None:
@ -248,7 +247,9 @@ class PrinterOutputDevice(QObject, OutputDevice):
if printer.printerConfiguration is not None and printer.printerConfiguration.hasAnyMaterialLoaded(): if printer.printerConfiguration is not None and printer.printerConfiguration.hasAnyMaterialLoaded():
all_configurations.add(printer.printerConfiguration) all_configurations.add(printer.printerConfiguration)
all_configurations.update(printer.availableConfigurations) all_configurations.update(printer.availableConfigurations)
if None in all_configurations: # Shouldn't happen, but it does. I don't see how it could ever happen. Skip adding that configuration. List could end up empty! if None in all_configurations:
# Shouldn't happen, but it does. I don't see how it could ever happen. Skip adding that configuration.
# List could end up empty!
Logger.log("e", "Found a broken configuration in the synced list!") Logger.log("e", "Found a broken configuration in the synced list!")
all_configurations.remove(None) all_configurations.remove(None)
new_configurations = sorted(all_configurations, key = lambda config: config.printerType or "") new_configurations = sorted(all_configurations, key = lambda config: config.printerType or "")
@ -256,9 +257,9 @@ class PrinterOutputDevice(QObject, OutputDevice):
self._unique_configurations = new_configurations self._unique_configurations = new_configurations
self.uniqueConfigurationsChanged.emit() self.uniqueConfigurationsChanged.emit()
# Returns the unique configurations of the printers within this output device
@pyqtProperty("QStringList", notify = uniqueConfigurationsChanged) @pyqtProperty("QStringList", notify = uniqueConfigurationsChanged)
def uniquePrinterTypes(self) -> List[str]: def uniquePrinterTypes(self) -> List[str]:
""" Returns the unique configurations of the printers within this output device """
return list(sorted(set([configuration.printerType or "" for configuration in self._unique_configurations]))) return list(sorted(set([configuration.printerType or "" for configuration in self._unique_configurations])))
def _onPrintersChanged(self) -> None: def _onPrintersChanged(self) -> None:

View File

@ -1,52 +0,0 @@
from typing import List
from UM.Settings.ContainerStack import ContainerStack
from UM.Util import parseBool
from cura.PrinterOutput.PrinterOutputDevice import ConnectionType
from cura.Settings.GlobalStack import GlobalStack
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
from UM.Settings.ContainerRegistry import ContainerRegistry
class AbstractMachine(GlobalStack):
""" Represents a group of machines of the same type. This allows the user to select settings before selecting a printer. """
def __init__(self, container_id: str) -> None:
super().__init__(container_id)
self.setMetaDataEntry("type", "abstract_machine")
@classmethod
def getMachines(cls, abstract_machine: ContainerStack, online_only = False) -> List[ContainerStack]:
""" Fetches all container stacks that match definition_id with an abstract machine.
:param abstractMachine: The abstract machine stack.
:return: A list of Containers or an empty list if abstract_machine is not an "abstract_machine"
"""
if not abstract_machine.getMetaDataEntry("type") == "abstract_machine":
return []
from cura.CuraApplication import CuraApplication # In function to avoid circular import
application = CuraApplication.getInstance()
registry = application.getContainerRegistry()
machines = registry.findContainerStacks(type="machine")
# Filter machines that match definition
machines = filter(lambda machine: machine.definition.id == abstract_machine.definition.getId(), machines)
# Filter only LAN and Cloud printers
machines = filter(lambda machine: ConnectionType.CloudConnection in machine.configuredConnectionTypes or ConnectionType.NetworkConnection in machine.configuredConnectionTypes, machines)
if online_only:
# LAN printers have is_online = False but should still be included
machines = filter(lambda machine: parseBool(machine.getMetaDataEntry("is_online", False) or ConnectionType.NetworkConnection in machine.configuredConnectionTypes), machines)
return list(machines)
## private:
_abstract_machine_mime = MimeType(
name = "application/x-cura-abstract-machine",
comment = "Cura Abstract Machine",
suffixes = ["global.cfg"]
)
MimeTypeDatabase.addMimeType(_abstract_machine_mime)
ContainerRegistry.addContainerTypeByName(AbstractMachine, "abstract_machine", _abstract_machine_mime.name)

View File

@ -1,7 +1,7 @@
# Copyright (c) 2019 Ultimaker B.V. # Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional from typing import Optional, cast
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
from UM.Logger import Logger from UM.Logger import Logger
@ -9,7 +9,6 @@ from UM.Settings.Interfaces import DefinitionContainerInterface
from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.InstanceContainer import InstanceContainer
from cura.Machines.ContainerTree import ContainerTree from cura.Machines.ContainerTree import ContainerTree
from .AbstractMachine import AbstractMachine
from .GlobalStack import GlobalStack from .GlobalStack import GlobalStack
from .ExtruderStack import ExtruderStack from .ExtruderStack import ExtruderStack
@ -268,48 +267,34 @@ class CuraStackBuilder:
return definition_changes_container return definition_changes_container
@classmethod @classmethod
def createAbstractMachine(cls, definition_id: str) -> Optional[AbstractMachine]: def createAbstractMachine(cls, definition_id: str) -> Optional[GlobalStack]:
"""Create a new instance of an abstract machine. """Create a new instance of an abstract machine.
:param definition_id: The ID of the machine definition to use. :param definition_id: The ID of the machine definition to use.
:return: The new Abstract Machine or None if an error occurred. :return: The new Abstract Machine or None if an error occurred.
""" """
abstract_machine_id = definition_id + "_abstract_machine" abstract_machine_id = f"{definition_id}_abstract_machine"
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
application = CuraApplication.getInstance() application = CuraApplication.getInstance()
registry = application.getContainerRegistry() registry = application.getContainerRegistry()
container_tree = ContainerTree.getInstance()
if registry.findContainerStacks(type = "abstract_machine", id = abstract_machine_id): abstract_machines = registry.findContainerStacks(id = abstract_machine_id)
# This abstract machine already exists if abstract_machines:
return cast(GlobalStack, abstract_machines[0])
definitions = registry.findDefinitionContainers(id=definition_id)
name = ""
if definitions:
name = definitions[0].getName()
stack = cls.createMachine(abstract_machine_id, definition_id)
if not stack:
return None return None
match registry.findDefinitionContainers(type = "machine", id = definition_id):
case []:
# It should not be possible for the definition to be missing since an abstract machine will only
# be created as a result of a machine with definition_id being created.
Logger.error(f"Definition {definition_id} was not found!")
return None
case [machine_definition, *_definitions]:
machine_node = container_tree.machines[machine_definition.getId()]
name = machine_definition.getName()
stack = AbstractMachine(abstract_machine_id)
stack.setMetaDataEntry("is_online", True)
stack.setDefinition(machine_definition)
cls.createUserContainer(
name,
machine_definition,
stack,
application.empty_variant_container,
application.empty_material_container,
machine_node.preferredGlobalQuality().container,
)
stack.setName(name) stack.setName(name)
registry.addContainer(stack) stack.setMetaDataEntry("is_abstract_machine", True)
stack.setMetaDataEntry("is_online", True)
return stack return stack

View File

@ -1,4 +1,4 @@
# Copyright (c) 2019 Ultimaker B.V. # Copyright (c) 2022 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from collections import defaultdict from collections import defaultdict
@ -8,10 +8,9 @@ import uuid
from PyQt6.QtCore import pyqtProperty, pyqtSlot, pyqtSignal from PyQt6.QtCore import pyqtProperty, pyqtSlot, pyqtSignal
from UM.Decorators import deprecated, override from UM.Decorators import override
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
from UM.Settings.ContainerStack import ContainerStack from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.SettingInstance import InstanceState
from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.Interfaces import PropertyEvaluationContext from UM.Settings.Interfaces import PropertyEvaluationContext
from UM.Logger import Logger from UM.Logger import Logger
@ -91,7 +90,6 @@ class GlobalStack(CuraContainerStack):
@pyqtProperty("QVariantList", notify=configuredConnectionTypesChanged) @pyqtProperty("QVariantList", notify=configuredConnectionTypesChanged)
def configuredConnectionTypes(self) -> List[int]: def configuredConnectionTypes(self) -> List[int]:
"""The configured connection types can be used to find out if the global """The configured connection types can be used to find out if the global
stack is configured to be connected with a printer, without having to stack is configured to be connected with a printer, without having to
know all the details as to how this is exactly done (and without know all the details as to how this is exactly done (and without
actually setting the stack to be active). actually setting the stack to be active).
@ -293,7 +291,6 @@ class GlobalStack(CuraContainerStack):
for extruder_train in extruder_trains: for extruder_train in extruder_trains:
extruder_position = extruder_train.getMetaDataEntry("position") extruder_position = extruder_train.getMetaDataEntry("position")
extruder_check_position.add(extruder_position) extruder_check_position.add(extruder_position)
for check_position in range(machine_extruder_count): for check_position in range(machine_extruder_count):
if str(check_position) not in extruder_check_position: if str(check_position) not in extruder_check_position:
return False return False
@ -344,14 +341,12 @@ class GlobalStack(CuraContainerStack):
def getName(self) -> str: def getName(self) -> str:
return self._metadata.get("group_name", self._metadata.get("name", "")) return self._metadata.get("group_name", self._metadata.get("name", ""))
def setName(self, name: "str") -> None: def setName(self, name: str) -> None:
super().setName(name) super().setName(name)
nameChanged = pyqtSignal() nameChanged = pyqtSignal()
name = pyqtProperty(str, fget=getName, fset=setName, notify=nameChanged) name = pyqtProperty(str, fget=getName, fset=setName, notify=nameChanged)
## private: ## private:
global_stack_mime = MimeType( global_stack_mime = MimeType(
name = "application/x-cura-globalstack", name = "application/x-cura-globalstack",

View File

@ -1,4 +1,4 @@
# Copyright (c) 2021 Ultimaker B.V. # Copyright (c) 2022 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
import time import time
@ -19,6 +19,7 @@ from UM.Logger import Logger
from UM.Message import Message from UM.Message import Message
from UM.Settings.SettingFunction import SettingFunction from UM.Settings.SettingFunction import SettingFunction
from UM.Settings.ContainerStack import ContainerStack
from UM.Signal import postponeSignals, CompressTechnique from UM.Signal import postponeSignals, CompressTechnique
import cura.CuraApplication # Imported like this to prevent circular references. import cura.CuraApplication # Imported like this to prevent circular references.
@ -186,6 +187,32 @@ class MachineManager(QObject):
self.outputDevicesChanged.emit() self.outputDevicesChanged.emit()
def getMachinesWithDefinition(self, definition_id: str, online_only=False) -> List[ContainerStack]:
""" Fetches all container stacks that match definition_id.
:param definition_id: The id of the machine definition.
:return: A list of Containers that match definition_id
"""
from cura.CuraApplication import CuraApplication # In function to avoid circular import
application = CuraApplication.getInstance()
registry = application.getContainerRegistry()
machines = registry.findContainerStacks(type="machine")
# Filter machines that match definition
machines = filter(lambda machine: machine.definition.id == definition_id, machines)
# Filter only LAN and Cloud printers
machines = filter(lambda machine: ConnectionType.CloudConnection in machine.configuredConnectionTypes or
ConnectionType.NetworkConnection in machine.configuredConnectionTypes,
machines)
if online_only:
# LAN printers can have is_online = False but should still be included,
# their online status is only checked when they are the active printer.
machines = filter(lambda machine: parseBool(machine.getMetaDataEntry("is_online", False) or
ConnectionType.NetworkConnection in machine.configuredConnectionTypes),
machines)
return list(machines)
@pyqtProperty(QObject, notify = currentConfigurationChanged) @pyqtProperty(QObject, notify = currentConfigurationChanged)
def currentConfiguration(self) -> PrinterConfigurationModel: def currentConfiguration(self) -> PrinterConfigurationModel:
return self._current_printer_configuration return self._current_printer_configuration
@ -332,6 +359,7 @@ class MachineManager(QObject):
extruder_manager = ExtruderManager.getInstance() extruder_manager = ExtruderManager.getInstance()
extruder_manager.fixSingleExtrusionMachineExtruderDefinition(global_stack) extruder_manager.fixSingleExtrusionMachineExtruderDefinition(global_stack)
if not global_stack.isValid(): if not global_stack.isValid():
Logger.warning("Global stack isn't valid, adding it to faulty container list")
# Mark global stack as invalid # Mark global stack as invalid
ConfigurationErrorMessage.getInstance().addFaultyContainers(global_stack.getId()) ConfigurationErrorMessage.getInstance().addFaultyContainers(global_stack.getId())
return # We're done here return # We're done here
@ -503,6 +531,10 @@ class MachineManager(QObject):
def printerConnected(self) -> bool: def printerConnected(self) -> bool:
return bool(self._printer_output_devices) return bool(self._printer_output_devices)
@pyqtProperty(bool, notify = globalContainerChanged)
def activeMachineIsAbstractCloudPrinter(self) -> bool:
return len(self._printer_output_devices) == 1 and self._printer_output_devices[0].__class__.__name__ == "AbstractCloudOutputDevice"
@pyqtProperty(bool, notify = printerConnectedStatusChanged) @pyqtProperty(bool, notify = printerConnectedStatusChanged)
def activeMachineIsGroup(self) -> bool: def activeMachineIsGroup(self) -> bool:
if self.activeMachine is None: if self.activeMachine is None:

View File

@ -156,6 +156,7 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter):
"connection_type", "connection_type",
"capabilities", "capabilities",
"octoprint_api_key", "octoprint_api_key",
"is_online",
} }
serialized_data = container.serialize(ignored_metadata_keys = ignore_keys) serialized_data = container.serialize(ignored_metadata_keys = ignore_keys)

View File

@ -1,4 +1,4 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2022 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher. // Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10 import QtQuick 2.10
@ -114,6 +114,7 @@ Rectangle
font: UM.Theme.getFont("medium") font: UM.Theme.getFont("medium")
width: contentWidth width: contentWidth
} }
Item Item
{ {
anchors anchors

View File

@ -153,7 +153,7 @@ Item
MonitorPrinterPill MonitorPrinterPill
{ {
text: printJob.configuration.printerType text: printJob ? printJob.configuration.printerType : ""
} }
} }
} }
@ -173,7 +173,7 @@ Item
id: printerConfiguration id: printerConfiguration
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
buildplate: catalog.i18nc("@label", "Glass") buildplate: catalog.i18nc("@label", "Glass")
configurations: base.printJob.configuration.extruderConfigurations configurations: base.printJob ? base.printJob.configuration.extruderConfigurations : null
height: Math.round(72 * screenScaleFactor) // TODO: Theme! height: Math.round(72 * screenScaleFactor) // TODO: Theme!
} }

View File

@ -1,8 +1,8 @@
// Copyright (c) 2019 Ultimaker B.V. // Copyright (c) 2022 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher. // Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2 import QtQuick 2.15
import UM 1.3 as UM import UM 1.5 as UM
import Cura 1.0 as Cura import Cura 1.0 as Cura
// This is the root component for the monitor stage. // This is the root component for the monitor stage.
@ -37,6 +37,7 @@ Component
Item Item
{ {
id: printers id: printers
visible: !Cura.MachineManager.activeMachineIsAbstractCloudPrinter
anchors anchors
{ {
top: parent.top top: parent.top
@ -69,14 +70,66 @@ Component
top: printers.bottom top: printers.bottom
topMargin: 48 * screenScaleFactor // TODO: Theme! topMargin: 48 * screenScaleFactor // TODO: Theme!
} }
visible: OutputDevice.supportsPrintJobQueue && OutputDevice.canReadPrintJobs visible: OutputDevice.supportsPrintJobQueue && OutputDevice.canReadPrintJobs && !Cura.MachineManager.activeMachineIsAbstractCloudPrinter
} }
PrinterVideoStream PrinterVideoStream
{ {
anchors.fill: parent anchors.fill: parent
cameraUrl: OutputDevice.activeCameraUrl cameraUrl: OutputDevice.activeCameraUrl
visible: OutputDevice.activeCameraUrl != "" visible: OutputDevice.activeCameraUrl != "" && !Cura.MachineManager.activeMachineIsAbstractCloudPrinter
}
Rectangle
{
id: sendToFactoryCard
visible: Cura.MachineManager.activeMachineIsAbstractCloudPrinter
color: UM.Theme.getColor("monitor_stage_background")
height: childrenRect.height + UM.Theme.getSize("default_margin").height * 2
width: childrenRect.width + UM.Theme.getSize("wide_margin").width * 2
anchors
{
horizontalCenter: parent.horizontalCenter
top: parent.top
topMargin: UM.Theme.getSize("wide_margin").height * screenScaleFactor * 2
}
Column
{
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
spacing: UM.Theme.getSize("wide_margin").height
padding: UM.Theme.getSize("default_margin").width
topPadding: 0
Image
{
id: sendToFactoryImage
anchors.horizontalCenter: parent.horizontalCenter
source: UM.Theme.getImage("cura_connected_printers")
}
UM.Label
{
anchors.horizontalCenter: parent.horizontalCenter
text: catalog.i18nc("@info", "Monitor your printers from everywhere using Ultimaker Digital Factory")
font: UM.Theme.getFont("medium")
width: sendToFactoryImage.width
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
Cura.PrimaryButton
{
id: sendToFactoryButton
anchors.horizontalCenter: parent.horizontalCenter
text: catalog.i18nc("@button", "View printers in Digital Factory")
onClicked: Qt.openUrlExternally("https://digitalfactory.ultimaker.com/app/print-jobs?utm_source=cura&utm_medium=software&utm_campaign=monitor-view-cloud-printer-type")
}
}
} }
} }
} }

View File

@ -0,0 +1,87 @@
from time import time
from typing import List
from PyQt6.QtCore import QObject
from PyQt6.QtNetwork import QNetworkReply
from UM import i18nCatalog
from UM.Logger import Logger
from cura.PrinterOutput.NetworkedPrinterOutputDevice import AuthState
from cura.PrinterOutput.PrinterOutputDevice import ConnectionType
from .CloudApiClient import CloudApiClient
from ..Models.Http.CloudClusterResponse import CloudClusterResponse
from ..Models.Http.CloudClusterWithConfigResponse import CloudClusterWithConfigResponse
from ..UltimakerNetworkedPrinterOutputDevice import UltimakerNetworkedPrinterOutputDevice
I18N_CATALOG = i18nCatalog("cura")
class AbstractCloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
API_CHECK_INTERVAL = 10.0 # seconds
def __init__(self, api_client: CloudApiClient, printer_type: str, parent: QObject = None) -> None:
self._api = api_client
properties = {b"printer_type": printer_type.encode()}
super().__init__(
device_id=f"ABSTRACT_{printer_type}",
address="",
connection_type=ConnectionType.CloudConnection,
properties=properties,
parent=parent
)
self._setInterfaceElements()
def connect(self) -> None:
"""Connects this device."""
if self.isConnected():
return
Logger.log("i", "Attempting to connect AbstractCloudOutputDevice %s", self.key)
super().connect()
#CuraApplication.getInstance().getBackend().backendStateChange.connect(self._onBackendStateChange)
self._update()
def disconnect(self) -> None:
"""Disconnects the device"""
if not self.isConnected():
return
super().disconnect()
def _update(self) -> None:
"""Called when the network data should be updated."""
super()._update()
if time() - self._time_of_last_request < self.API_CHECK_INTERVAL:
return # avoid calling the cloud too often
self._time_of_last_request = time()
if self._api.account.isLoggedIn:
self.setAuthenticationState(AuthState.Authenticated)
self._last_request_time = time()
self._api.getClustersByMachineType(self.printerType, self._onCompleted, self._onError)
else:
self.setAuthenticationState(AuthState.NotAuthenticated)
def _setInterfaceElements(self) -> None:
"""Set all the interface elements and texts for this output device."""
self.setPriority(2) # Make sure we end up below the local networking and above 'save to file'.
self.setShortDescription(I18N_CATALOG.i18nc("@action:button", "Print via cloud"))
self.setDescription(I18N_CATALOG.i18nc("@properties:tooltip", "Print via cloud"))
self.setConnectionText(I18N_CATALOG.i18nc("@info:status", "Connected via cloud"))
def _onCompleted(self, clusters: List[CloudClusterWithConfigResponse]) -> None:
self._responseReceived()
all_configurations = []
for resp in clusters:
if resp.configuration is not None:
# Usually when the printer is offline, it doesn't have a configuration...
all_configurations.append(resp.configuration)
self._updatePrinters(all_configurations)
def _onError(self, reply: QNetworkReply, error: QNetworkReply.NetworkError) -> None:
pass

View File

@ -1,6 +1,7 @@
# Copyright (c) 2019 Ultimaker B.V. # Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
import json import json
import urllib.parse
from json import JSONDecodeError from json import JSONDecodeError
from time import time from time import time
from typing import Callable, List, Type, TypeVar, Union, Optional, Tuple, Dict, Any, cast from typing import Callable, List, Type, TypeVar, Union, Optional, Tuple, Dict, Any, cast
@ -17,6 +18,7 @@ from cura.UltimakerCloud import UltimakerCloudConstants
from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope
from .ToolPathUploader import ToolPathUploader from .ToolPathUploader import ToolPathUploader
from ..Models.BaseModel import BaseModel from ..Models.BaseModel import BaseModel
from ..Models.Http.CloudClusterWithConfigResponse import CloudClusterWithConfigResponse
from ..Models.Http.CloudClusterResponse import CloudClusterResponse from ..Models.Http.CloudClusterResponse import CloudClusterResponse
from ..Models.Http.CloudClusterStatus import CloudClusterStatus from ..Models.Http.CloudClusterStatus import CloudClusterStatus
from ..Models.Http.CloudError import CloudError from ..Models.Http.CloudError import CloudError
@ -48,7 +50,6 @@ class CloudApiClient:
"""Initializes a new cloud API client. """Initializes a new cloud API client.
:param app: :param app:
:param account: The user's account object
:param on_error: The callback to be called whenever we receive errors from the server. :param on_error: The callback to be called whenever we receive errors from the server.
""" """
super().__init__() super().__init__()
@ -57,12 +58,11 @@ class CloudApiClient:
self._scope = JsonDecoratorScope(UltimakerCloudScope(app)) self._scope = JsonDecoratorScope(UltimakerCloudScope(app))
self._http = HttpRequestManager.getInstance() self._http = HttpRequestManager.getInstance()
self._on_error = on_error self._on_error = on_error
self._upload = None # type: Optional[ToolPathUploader] self._upload: Optional[ToolPathUploader] = None
@property @property
def account(self) -> Account: def account(self) -> Account:
"""Gets the account used for the API.""" """Gets the account used for the API."""
return self._account return self._account
def getClusters(self, on_finished: Callable[[List[CloudClusterResponse]], Any], failed: Callable) -> None: def getClusters(self, on_finished: Callable[[List[CloudClusterResponse]], Any], failed: Callable) -> None:
@ -71,13 +71,31 @@ class CloudApiClient:
:param on_finished: The function to be called after the result is parsed. :param on_finished: The function to be called after the result is parsed.
""" """
url = "{}/clusters?status=active".format(self.CLUSTER_API_ROOT) url = f"{self.CLUSTER_API_ROOT}/clusters?status=active"
self._http.get(url, self._http.get(url,
scope = self._scope, scope = self._scope,
callback = self._parseCallback(on_finished, CloudClusterResponse, failed), callback = self._parseCallback(on_finished, CloudClusterResponse, failed),
error_callback = failed, error_callback = failed,
timeout = self.DEFAULT_REQUEST_TIMEOUT) timeout = self.DEFAULT_REQUEST_TIMEOUT)
def getClustersByMachineType(self, machine_type, on_finished: Callable[[List[CloudClusterWithConfigResponse]], Any], failed: Callable) -> None:
# HACK: There is something weird going on with the API, as it reports printer types in formats like
# "ultimaker_s3", but wants "Ultimaker S3" when using the machine_variant filter query. So we need to do some
# conversion!
machine_type = machine_type.replace("_plus", "+")
machine_type = machine_type.replace("_", " ")
machine_type = machine_type.replace("ultimaker", "ultimaker ")
machine_type = machine_type.replace(" ", " ")
machine_type = machine_type.title()
machine_type = urllib.parse.quote_plus(machine_type)
url = f"{self.CLUSTER_API_ROOT}/clusters?machine_variant={machine_type}"
self._http.get(url,
scope=self._scope,
callback=self._parseCallback(on_finished, CloudClusterWithConfigResponse, failed),
error_callback=failed,
timeout=self.DEFAULT_REQUEST_TIMEOUT)
def getClusterStatus(self, cluster_id: str, on_finished: Callable[[CloudClusterStatus], Any]) -> None: def getClusterStatus(self, cluster_id: str, on_finished: Callable[[CloudClusterStatus], Any]) -> None:
"""Retrieves the status of the given cluster. """Retrieves the status of the given cluster.
@ -85,7 +103,7 @@ class CloudApiClient:
:param on_finished: The function to be called after the result is parsed. :param on_finished: The function to be called after the result is parsed.
""" """
url = "{}/clusters/{}/status".format(self.CLUSTER_API_ROOT, cluster_id) url = f"{self.CLUSTER_API_ROOT}/clusters/{cluster_id}/status"
self._http.get(url, self._http.get(url,
scope = self._scope, scope = self._scope,
callback = self._parseCallback(on_finished, CloudClusterStatus), callback = self._parseCallback(on_finished, CloudClusterStatus),
@ -100,7 +118,7 @@ class CloudApiClient:
:param on_finished: The function to be called after the result is parsed. :param on_finished: The function to be called after the result is parsed.
""" """
url = "{}/jobs/upload".format(self.CURA_API_ROOT) url = f"{self.CURA_API_ROOT}/jobs/upload"
data = json.dumps({"data": request.toDict()}).encode() data = json.dumps({"data": request.toDict()}).encode()
self._http.put(url, self._http.put(url,
@ -131,7 +149,7 @@ class CloudApiClient:
# specific to sending print jobs) such as lost connection, unparsable responses, etc. are not returned here, but # specific to sending print jobs) such as lost connection, unparsable responses, etc. are not returned here, but
# handled in a generic way by the CloudApiClient. # handled in a generic way by the CloudApiClient.
def requestPrint(self, cluster_id: str, job_id: str, on_finished: Callable[[CloudPrintResponse], Any], on_error) -> None: def requestPrint(self, cluster_id: str, job_id: str, on_finished: Callable[[CloudPrintResponse], Any], on_error) -> None:
url = "{}/clusters/{}/print/{}".format(self.CLUSTER_API_ROOT, cluster_id, job_id) url = f"{self.CLUSTER_API_ROOT}/clusters/{cluster_id}/print/{job_id}"
self._http.post(url, self._http.post(url,
scope = self._scope, scope = self._scope,
data = b"", data = b"",
@ -150,7 +168,7 @@ class CloudApiClient:
""" """
body = json.dumps({"data": data}).encode() if data else b"" body = json.dumps({"data": data}).encode() if data else b""
url = "{}/clusters/{}/print_jobs/{}/action/{}".format(self.CLUSTER_API_ROOT, cluster_id, cluster_job_id, action) url = f"{self.CLUSTER_API_ROOT}/clusters/{cluster_id}/print_jobs/{cluster_job_id}/action/{action}"
self._http.post(url, self._http.post(url,
scope = self._scope, scope = self._scope,
data = body, data = body,
@ -159,7 +177,7 @@ class CloudApiClient:
def _createEmptyRequest(self, path: str, content_type: Optional[str] = "application/json") -> QNetworkRequest: def _createEmptyRequest(self, path: str, content_type: Optional[str] = "application/json") -> QNetworkRequest:
"""We override _createEmptyRequest in order to add the user credentials. """We override _createEmptyRequest in order to add the user credentials.
:param url: The URL to request :param path: The URL to request
:param content_type: The type of the body contents. :param content_type: The type of the body contents.
""" """
@ -168,7 +186,7 @@ class CloudApiClient:
request.setHeader(QNetworkRequest.KnownHeaders.ContentTypeHeader, content_type) request.setHeader(QNetworkRequest.KnownHeaders.ContentTypeHeader, content_type)
access_token = self._account.accessToken access_token = self._account.accessToken
if access_token: if access_token:
request.setRawHeader(b"Authorization", "Bearer {}".format(access_token).encode()) request.setRawHeader(b"Authorization", f"Bearer {access_token}".encode())
return request return request
@staticmethod @staticmethod
@ -189,9 +207,9 @@ class CloudApiClient:
Logger.logException("e", "Could not parse the stardust response: %s", error.toDict()) Logger.logException("e", "Could not parse the stardust response: %s", error.toDict())
return status_code, {"errors": [error.toDict()]} return status_code, {"errors": [error.toDict()]}
def _parseModels(self, response: Dict[str, Any], on_finished: Union[Callable[[CloudApiClientModel], Any], def _parseResponse(self, response: Dict[str, Any], on_finished: Union[Callable[[CloudApiClientModel], Any],
Callable[[List[CloudApiClientModel]], Any]], model_class: Type[CloudApiClientModel]) -> None: Callable[[List[CloudApiClientModel]], Any]], model_class: Type[CloudApiClientModel]) -> None:
"""Parses the given models and calls the correct callback depending on the result. """Parses the given response and calls the correct callback depending on the result.
:param response: The response from the server, after being converted to a dict. :param response: The response from the server, after being converted to a dict.
:param on_finished: The callback in case the response is successful. :param on_finished: The callback in case the response is successful.
@ -200,7 +218,10 @@ class CloudApiClient:
if "data" in response: if "data" in response:
data = response["data"] data = response["data"]
if isinstance(data, list): if "status" in data and data["status"] == "wait_approval":
on_finished_empty = cast(Callable[[List], Any], on_finished)
on_finished_empty([])
elif isinstance(data, list):
results = [model_class(**c) for c in data] # type: List[CloudApiClientModel] results = [model_class(**c) for c in data] # type: List[CloudApiClientModel]
on_finished_list = cast(Callable[[List[CloudApiClientModel]], Any], on_finished) on_finished_list = cast(Callable[[List[CloudApiClientModel]], Any], on_finished)
on_finished_list(results) on_finished_list(results)
@ -242,7 +263,7 @@ class CloudApiClient:
if status_code >= 300 and on_error is not None: if status_code >= 300 and on_error is not None:
on_error() on_error()
else: else:
self._parseModels(response, on_finished, model) self._parseResponse(response, on_finished, model)
self._anti_gc_callbacks.append(parse) self._anti_gc_callbacks.append(parse)
return parse return parse

View File

@ -3,7 +3,7 @@
from time import time from time import time
import os import os
from typing import cast, List, Optional, TYPE_CHECKING from typing import cast, List, Optional
from PyQt6.QtCore import QObject, QUrl, pyqtProperty, pyqtSignal, pyqtSlot from PyQt6.QtCore import QObject, QUrl, pyqtProperty, pyqtSignal, pyqtSlot
from PyQt6.QtGui import QDesktopServices from PyQt6.QtGui import QDesktopServices
@ -21,6 +21,7 @@ from cura.PrinterOutput.PrinterOutputDevice import ConnectionType
from .CloudApiClient import CloudApiClient from .CloudApiClient import CloudApiClient
from ..ExportFileJob import ExportFileJob from ..ExportFileJob import ExportFileJob
from ..Messages.PrintJobAwaitingApprovalMessage import PrintJobPendingApprovalMessage
from ..UltimakerNetworkedPrinterOutputDevice import UltimakerNetworkedPrinterOutputDevice from ..UltimakerNetworkedPrinterOutputDevice import UltimakerNetworkedPrinterOutputDevice
from ..Messages.PrintJobUploadBlockedMessage import PrintJobUploadBlockedMessage from ..Messages.PrintJobUploadBlockedMessage import PrintJobUploadBlockedMessage
from ..Messages.PrintJobUploadErrorMessage import PrintJobUploadErrorMessage from ..Messages.PrintJobUploadErrorMessage import PrintJobUploadErrorMessage
@ -41,7 +42,7 @@ I18N_CATALOG = i18nCatalog("cura")
class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
"""The cloud output device is a network output device that works remotely but has limited functionality. """The cloud output device is a network output device that works remotely but has limited functionality.
Currently it only supports viewing the printer and print job status and adding a new job to the queue. Currently, it only supports viewing the printer and print job status and adding a new job to the queue.
As such, those methods have been implemented here. As such, those methods have been implemented here.
Note that this device represents a single remote cluster, not a list of multiple clusters. Note that this device represents a single remote cluster, not a list of multiple clusters.
""" """
@ -58,7 +59,7 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
PRINT_JOB_ACTIONS_MIN_VERSION = Version("5.2.12") PRINT_JOB_ACTIONS_MIN_VERSION = Version("5.2.12")
# Notify can only use signals that are defined by the class that they are in, not inherited ones. # Notify can only use signals that are defined by the class that they are in, not inherited ones.
# Therefore we create a private signal used to trigger the printersChanged signal. # Therefore, we create a private signal used to trigger the printersChanged signal.
_cloudClusterPrintersChanged = pyqtSignal() _cloudClusterPrintersChanged = pyqtSignal()
def __init__(self, api_client: CloudApiClient, cluster: CloudClusterResponse, parent: QObject = None) -> None: def __init__(self, api_client: CloudApiClient, cluster: CloudClusterResponse, parent: QObject = None) -> None:
@ -202,7 +203,8 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
# Note that self.writeFinished is called in _onPrintUploadCompleted as well. # Note that self.writeFinished is called in _onPrintUploadCompleted as well.
if self._uploaded_print_job: if self._uploaded_print_job:
Logger.log("i", "Current mesh is already attached to a print-job, immediately request reprint.") Logger.log("i", "Current mesh is already attached to a print-job, immediately request reprint.")
self._api.requestPrint(self.key, self._uploaded_print_job.job_id, self._onPrintUploadCompleted, self._onPrintUploadSpecificError) self._api.requestPrint(self.key, self._uploaded_print_job.job_id, self._onPrintUploadCompleted,
self._onPrintUploadSpecificError)
return return
# Export the scene to the correct file type. # Export the scene to the correct file type.
@ -230,6 +232,7 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
:param job_response: The response received from the cloud API. :param job_response: The response received from the cloud API.
""" """
if not self._tool_path: if not self._tool_path:
return self._onUploadError() return self._onUploadError()
self._pre_upload_print_job = job_response # store the last uploaded job to prevent re-upload of the same file self._pre_upload_print_job = job_response # store the last uploaded job to prevent re-upload of the same file
@ -244,12 +247,15 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
self._progress.update(100) self._progress.update(100)
print_job = cast(CloudPrintJobResponse, self._pre_upload_print_job) print_job = cast(CloudPrintJobResponse, self._pre_upload_print_job)
if not print_job: # It's possible that another print job is requested in the meanwhile, which then fails to upload with an error, which sets self._pre_uploaded_print_job to `None`. if not print_job:
# It's possible that another print job is requested in the meanwhile, which then fails to upload with an
# error, which sets self._pre_uploaded_print_job to `None`.
self._pre_upload_print_job = None self._pre_upload_print_job = None
self._uploaded_print_job = None self._uploaded_print_job = None
Logger.log("w", "Interference from another job uploaded at roughly the same time, not uploading print!") Logger.log("w", "Interference from another job uploaded at roughly the same time, not uploading print!")
return # Prevent a crash. return # Prevent a crash.
self._api.requestPrint(self.key, print_job.job_id, self._onPrintUploadCompleted, self._onPrintUploadSpecificError) self._api.requestPrint(self.key, print_job.job_id, self._onPrintUploadCompleted,
self._onPrintUploadSpecificError)
def _onPrintUploadCompleted(self, response: CloudPrintResponse) -> None: def _onPrintUploadCompleted(self, response: CloudPrintResponse) -> None:
"""Shows a message when the upload has succeeded """Shows a message when the upload has succeeded
@ -258,6 +264,8 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
""" """
self._uploaded_print_job = self._pre_upload_print_job self._uploaded_print_job = self._pre_upload_print_job
self._progress.hide() self._progress.hide()
if response:
message = PrintJobUploadSuccessMessage() message = PrintJobUploadSuccessMessage()
message.addAction("monitor print", message.addAction("monitor print",
name=I18N_CATALOG.i18nc("@action:button", "Monitor print"), name=I18N_CATALOG.i18nc("@action:button", "Monitor print"),
@ -268,6 +276,9 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
message.pyQtActionTriggered.connect(lambda message, action: (QDesktopServices.openUrl(QUrl(df_url)), message.hide())) message.pyQtActionTriggered.connect(lambda message, action: (QDesktopServices.openUrl(QUrl(df_url)), message.hide()))
message.show() message.show()
else:
PrintJobPendingApprovalMessage(self._cluster.cluster_id).show()
self.writeFinished.emit() self.writeFinished.emit()
def _onPrintUploadSpecificError(self, reply: "QNetworkReply", _: "QNetworkReply.NetworkError"): def _onPrintUploadSpecificError(self, reply: "QNetworkReply", _: "QNetworkReply.NetworkError"):
@ -278,7 +289,9 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
if error_code == 409: if error_code == 409:
PrintJobUploadQueueFullMessage().show() PrintJobUploadQueueFullMessage().show()
else: else:
PrintJobUploadErrorMessage(I18N_CATALOG.i18nc("@error:send", "Unknown error code when uploading print job: {0}", error_code)).show() PrintJobUploadErrorMessage(I18N_CATALOG.i18nc("@error:send",
"Unknown error code when uploading print job: {0}",
error_code)).show()
Logger.log("w", "Upload of print job failed specifically with error code {}".format(error_code)) Logger.log("w", "Upload of print job failed specifically with error code {}".format(error_code))
@ -336,11 +349,13 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
@pyqtSlot(name="openPrintJobControlPanel") @pyqtSlot(name="openPrintJobControlPanel")
def openPrintJobControlPanel(self) -> None: def openPrintJobControlPanel(self) -> None:
QDesktopServices.openUrl(QUrl(self.clusterCloudUrl + "?utm_source=cura&utm_medium=software&utm_campaign=monitor-manage-browser")) QDesktopServices.openUrl(QUrl(f"{self.clusterCloudUrl}?utm_source=cura&utm_medium=software&"
f"utm_campaign=monitor-manage-browser"))
@pyqtSlot(name="openPrinterControlPanel") @pyqtSlot(name="openPrinterControlPanel")
def openPrinterControlPanel(self) -> None: def openPrinterControlPanel(self) -> None:
QDesktopServices.openUrl(QUrl(self.clusterCloudUrl + "?utm_source=cura&utm_medium=software&utm_campaign=monitor-manage-printer")) QDesktopServices.openUrl(QUrl(f"{self.clusterCloudUrl}?utm_source=cura&utm_medium=software"
f"&utm_campaign=monitor-manage-printer"))
permissionsChanged = pyqtSignal() permissionsChanged = pyqtSignal()
@ -362,7 +377,7 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
@pyqtProperty(bool, notify = permissionsChanged) @pyqtProperty(bool, notify = permissionsChanged)
def canWriteOwnPrintJobs(self) -> bool: def canWriteOwnPrintJobs(self) -> bool:
""" """
Whether this user can change things about print jobs made by themself. Whether this user can change things about print jobs made by them.
""" """
return "digital-factory.print-job.write.own" in self._account.permissions return "digital-factory.print-job.write.own" in self._account.permissions
@ -390,4 +405,4 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
"""Gets the URL on which to monitor the cluster via the cloud.""" """Gets the URL on which to monitor the cluster via the cloud."""
root_url_prefix = "-staging" if self._account.is_staging else "" root_url_prefix = "-staging" if self._account.is_staging else ""
return "https://digitalfactory{}.ultimaker.com/app/jobs/{}".format(root_url_prefix, self.clusterData.cluster_id) return f"https://digitalfactory{root_url_prefix}.ultimaker.com/app/jobs/{self.clusterData.cluster_id}"

View File

@ -9,7 +9,6 @@ from PyQt6.QtWidgets import QMessageBox
from UM import i18nCatalog from UM import i18nCatalog
from UM.Logger import Logger # To log errors talking to the API. from UM.Logger import Logger # To log errors talking to the API.
from UM.Message import Message
from UM.Settings.Interfaces import ContainerInterface from UM.Settings.Interfaces import ContainerInterface
from UM.Signal import Signal from UM.Signal import Signal
from UM.Util import parseBool from UM.Util import parseBool
@ -20,16 +19,19 @@ from cura.Settings.CuraContainerRegistry import CuraContainerRegistry # To upda
from cura.Settings.CuraStackBuilder import CuraStackBuilder from cura.Settings.CuraStackBuilder import CuraStackBuilder
from cura.Settings.GlobalStack import GlobalStack from cura.Settings.GlobalStack import GlobalStack
from cura.UltimakerCloud.UltimakerCloudConstants import META_CAPABILITIES, META_UM_LINKED_TO_ACCOUNT from cura.UltimakerCloud.UltimakerCloudConstants import META_CAPABILITIES, META_UM_LINKED_TO_ACCOUNT
from .AbstractCloudOutputDevice import AbstractCloudOutputDevice
from .CloudApiClient import CloudApiClient from .CloudApiClient import CloudApiClient
from .CloudOutputDevice import CloudOutputDevice from .CloudOutputDevice import CloudOutputDevice
from ..Messages.RemovedPrintersMessage import RemovedPrintersMessage
from ..Models.Http.CloudClusterResponse import CloudClusterResponse from ..Models.Http.CloudClusterResponse import CloudClusterResponse
from ..Messages.NewPrinterDetectedMessage import NewPrinterDetectedMessage
class CloudOutputDeviceManager: class CloudOutputDeviceManager:
"""The cloud output device manager is responsible for using the Ultimaker Cloud APIs to manage remote clusters. """The cloud output device manager is responsible for using the Ultimaker Cloud APIs to manage remote clusters.
Keeping all cloud related logic in this class instead of the UM3OutputDevicePlugin results in more readable code. Keeping all cloud related logic in this class instead of the UM3OutputDevicePlugin results in more readable code.
API spec is available on https://api.ultimaker.com/docs/connect/spec/. API spec is available on https://docs.api.ultimaker.com/connect/index.html.
""" """
META_CLUSTER_ID = "um_cloud_cluster_id" META_CLUSTER_ID = "um_cloud_cluster_id"
@ -46,21 +48,22 @@ class CloudOutputDeviceManager:
def __init__(self) -> None: def __init__(self) -> None:
# Persistent dict containing the remote clusters for the authenticated user. # Persistent dict containing the remote clusters for the authenticated user.
self._remote_clusters = {} # type: Dict[str, CloudOutputDevice] self._remote_clusters: Dict[str, CloudOutputDevice] = {}
self._abstract_clusters: Dict[str, AbstractCloudOutputDevice] = {}
# Dictionary containing all the cloud printers loaded in Cura # Dictionary containing all the cloud printers loaded in Cura
self._um_cloud_printers = {} # type: Dict[str, GlobalStack] self._um_cloud_printers: Dict[str, GlobalStack] = {}
self._account = CuraApplication.getInstance().getCuraAPI().account # type: Account self._account: Account = CuraApplication.getInstance().getCuraAPI().account
self._api = CloudApiClient(CuraApplication.getInstance(), on_error = lambda error: Logger.log("e", str(error))) self._api = CloudApiClient(CuraApplication.getInstance(), on_error = lambda error: Logger.log("e", str(error)))
self._account.loginStateChanged.connect(self._onLoginStateChanged) self._account.loginStateChanged.connect(self._onLoginStateChanged)
self._removed_printers_message = None # type: Optional[Message] self._removed_printers_message: Optional[RemovedPrintersMessage] = None
# Ensure we don't start twice. # Ensure we don't start twice.
self._running = False self._running = False
self._syncing = False self._syncing = False
CuraApplication.getInstance().getContainerRegistry().containerRemoved.connect(self._printerRemoved) CuraApplication.getInstance().getContainerRegistry().containerRemoved.connect(self._printerRemoved)
def start(self): def start(self):
@ -113,8 +116,8 @@ class CloudOutputDeviceManager:
CuraApplication.getInstance().getContainerRegistry().findContainerStacks( CuraApplication.getInstance().getContainerRegistry().findContainerStacks(
type = "machine") if m.getMetaDataEntry(self.META_CLUSTER_ID, None)} type = "machine") if m.getMetaDataEntry(self.META_CLUSTER_ID, None)}
new_clusters = [] new_clusters = []
all_clusters = {c.cluster_id: c for c in clusters} # type: Dict[str, CloudClusterResponse] all_clusters: Dict[str, CloudClusterResponse] = {c.cluster_id: c for c in clusters}
online_clusters = {c.cluster_id: c for c in clusters if c.is_online} # type: Dict[str, CloudClusterResponse] online_clusters: Dict[str, CloudClusterResponse] = {c.cluster_id: c for c in clusters if c.is_online}
# Add the new printers in Cura. # Add the new printers in Cura.
for device_id, cluster_data in all_clusters.items(): for device_id, cluster_data in all_clusters.items():
@ -130,8 +133,11 @@ class CloudOutputDeviceManager:
self._um_cloud_printers[device_id].setMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, True) self._um_cloud_printers[device_id].setMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, True)
if not self._um_cloud_printers[device_id].getMetaDataEntry(META_CAPABILITIES, None): if not self._um_cloud_printers[device_id].getMetaDataEntry(META_CAPABILITIES, None):
self._um_cloud_printers[device_id].setMetaDataEntry(META_CAPABILITIES, ",".join(cluster_data.capabilities)) self._um_cloud_printers[device_id].setMetaDataEntry(META_CAPABILITIES, ",".join(cluster_data.capabilities))
self._onDevicesDiscovered(new_clusters)
# We want a machine stack per remote printer that we discovered. Create them now!
self._createMachineStacksForDiscoveredClusters(new_clusters)
# Update the online vs offline status for all found devices
self._updateOnlinePrinters(all_clusters) self._updateOnlinePrinters(all_clusters)
# Hide the current removed_printers_message, if there is any # Hide the current removed_printers_message, if there is any
@ -152,6 +158,7 @@ class CloudOutputDeviceManager:
if new_clusters or offline_device_keys or removed_device_keys: if new_clusters or offline_device_keys or removed_device_keys:
self.discoveredDevicesChanged.emit() self.discoveredDevicesChanged.emit()
if offline_device_keys: if offline_device_keys:
# If the removed device was active we should connect to the new active device # If the removed device was active we should connect to the new active device
self._connectToActiveMachine() self._connectToActiveMachine()
@ -165,54 +172,65 @@ class CloudOutputDeviceManager:
self._syncing = False self._syncing = False
self._account.setSyncState(self.SYNC_SERVICE_NAME, SyncState.ERROR) self._account.setSyncState(self.SYNC_SERVICE_NAME, SyncState.ERROR)
def _onDevicesDiscovered(self, clusters: List[CloudClusterResponse]) -> None: def _createMachineStacksForDiscoveredClusters(self, discovered_clusters: List[CloudClusterResponse]) -> None:
"""**Synchronously** create machines for discovered devices """**Synchronously** create machines for discovered devices
Any new machines are made available to the user. Any new machines are made available to the user.
May take a long time to complete. As this code needs access to the Application May take a long time to complete. This currently forcefully calls the "processEvents", which isn't
and blocks the GIL, creating a Job for this would not make sense. the nicest solution out there. We might need to consider moving this into a job later!
Shows a Message informing the user of progress.
""" """
new_devices = [] new_output_devices: List[CloudOutputDevice] = []
remote_clusters_added = False remote_clusters_added = False
host_guid_map = {machine.getMetaDataEntry(self.META_HOST_GUID): device_cluster_id
# Create a map that maps the HOST_GUID to the DEVICE_CLUSTER_ID
host_guid_map: Dict[str, str] = {machine.getMetaDataEntry(self.META_HOST_GUID): device_cluster_id
for device_cluster_id, machine in self._um_cloud_printers.items() for device_cluster_id, machine in self._um_cloud_printers.items()
if machine.getMetaDataEntry(self.META_HOST_GUID)} if machine.getMetaDataEntry(self.META_HOST_GUID)}
machine_manager = CuraApplication.getInstance().getMachineManager() machine_manager = CuraApplication.getInstance().getMachineManager()
for cluster_data in clusters: for cluster_data in discovered_clusters:
device = CloudOutputDevice(self._api, cluster_data) output_device = CloudOutputDevice(self._api, cluster_data)
if cluster_data.printer_type not in self._abstract_clusters:
self._abstract_clusters[cluster_data.printer_type] = AbstractCloudOutputDevice(self._api, cluster_data.printer_type)
# Ensure that the abstract machine is added (either because it was never added, or it somehow got
# removed)
_abstract_machine = CuraStackBuilder.createAbstractMachine(cluster_data.printer_type)
# If the machine already existed before, it will be present in the host_guid_map # If the machine already existed before, it will be present in the host_guid_map
if cluster_data.host_guid in host_guid_map: if cluster_data.host_guid in host_guid_map:
machine = machine_manager.getMachine(device.printerType, {self.META_HOST_GUID: cluster_data.host_guid}) machine = machine_manager.getMachine(output_device.printerType, {self.META_HOST_GUID: cluster_data.host_guid})
if machine and machine.getMetaDataEntry(self.META_CLUSTER_ID) != device.key: if machine and machine.getMetaDataEntry(self.META_CLUSTER_ID) != output_device.key:
# If the retrieved device has a different cluster_id than the existing machine, bring the existing # If the retrieved device has a different cluster_id than the existing machine, bring the existing
# machine up-to-date. # machine up-to-date.
self._updateOutdatedMachine(outdated_machine = machine, new_cloud_output_device = device) self._updateOutdatedMachine(outdated_machine = machine, new_cloud_output_device = output_device)
# Create a machine if we don't already have it. Do not make it the active machine. # Create a machine if we don't already have it. Do not make it the active machine.
# We only need to add it if it wasn't already added by "local" network or by cloud. # We only need to add it if it wasn't already added by "local" network or by cloud.
if machine_manager.getMachine(device.printerType, {self.META_CLUSTER_ID: device.key}) is None \ if machine_manager.getMachine(output_device.printerType, {self.META_CLUSTER_ID: output_device.key}) is None \
and machine_manager.getMachine(device.printerType, {self.META_NETWORK_KEY: cluster_data.host_name + "*"}) is None: # The host name is part of the network key. and machine_manager.getMachine(output_device.printerType, {self.META_NETWORK_KEY: cluster_data.host_name + "*"}) is None: # The host name is part of the network key.
new_devices.append(device) new_output_devices.append(output_device)
elif device.getId() not in self._remote_clusters: elif output_device.getId() not in self._remote_clusters:
self._remote_clusters[device.getId()] = device self._remote_clusters[output_device.getId()] = output_device
remote_clusters_added = True remote_clusters_added = True
# If a printer that was removed from the account is re-added, change its metadata to mark it not removed # If a printer that was removed from the account is re-added, change its metadata to mark it not removed
# from the account # from the account
elif not parseBool(self._um_cloud_printers[device.key].getMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, "true")): elif not parseBool(self._um_cloud_printers[output_device.key].getMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, "true")):
self._um_cloud_printers[device.key].setMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, True) self._um_cloud_printers[output_device.key].setMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, True)
# As adding a lot of machines might take some time, ensure that the GUI (and progress message) is updated
CuraApplication.getInstance().processEvents()
# Inform the Cloud printers model about new devices. # Inform the Cloud printers model about new devices.
new_devices_list_of_dicts = [{ new_devices_list_of_dicts = [{
"key": d.getId(), "key": d.getId(),
"name": d.name, "name": d.name,
"machine_type": d.printerTypeName, "machine_type": d.printerTypeName,
"firmware_version": d.firmwareVersion} for d in new_devices] "firmware_version": d.firmwareVersion} for d in new_output_devices]
discovered_cloud_printers_model = CuraApplication.getInstance().getDiscoveredCloudPrintersModel() discovered_cloud_printers_model = CuraApplication.getInstance().getDiscoveredCloudPrintersModel()
discovered_cloud_printers_model.addDiscoveredCloudPrinters(new_devices_list_of_dicts) discovered_cloud_printers_model.addDiscoveredCloudPrinters(new_devices_list_of_dicts)
if not new_devices: if not new_output_devices:
if remote_clusters_added: if remote_clusters_added:
self._connectToActiveMachine() self._connectToActiveMachine()
return return
@ -220,55 +238,29 @@ class CloudOutputDeviceManager:
# Sort new_devices on online status first, alphabetical second. # Sort new_devices on online status first, alphabetical second.
# Since the first device might be activated in case there is no active printer yet, # Since the first device might be activated in case there is no active printer yet,
# it would be nice to prioritize online devices # it would be nice to prioritize online devices
online_cluster_names = {c.friendly_name.lower() for c in clusters if c.is_online and not c.friendly_name is None} online_cluster_names = {c.friendly_name.lower() for c in discovered_clusters if c.is_online and not c.friendly_name is None}
new_devices.sort(key = lambda x: ("a{}" if x.name.lower() in online_cluster_names else "b{}").format(x.name.lower())) new_output_devices.sort(key = lambda x: ("a{}" if x.name.lower() in online_cluster_names else "b{}").format(x.name.lower()))
message = Message( message = NewPrinterDetectedMessage(num_printers_found = len(new_output_devices))
title = self.i18n_catalog.i18ncp(
"info:status",
"New printer detected from your Ultimaker account",
"New printers detected from your Ultimaker account",
len(new_devices)
),
progress = 0,
lifetime = 0,
message_type = Message.MessageType.POSITIVE
)
message.show() message.show()
new_devices_added = [] new_devices_added = []
for idx, device in enumerate(new_devices): for idx, output_device in enumerate(new_output_devices):
message_text = self.i18n_catalog.i18nc("info:status Filled in with printer name and printer model.", "Adding printer {name} ({model}) from your account").format(name = device.name, model = device.printerTypeName) message.updateProgressText(output_device)
message.setText(message_text)
if len(new_devices) > 1: self._remote_clusters[output_device.getId()] = output_device
message.setProgress((idx / len(new_devices)) * 100)
CuraApplication.getInstance().processEvents()
self._remote_clusters[device.getId()] = device
# If there is no active machine, activate the first available cloud printer # If there is no active machine, activate the first available cloud printer
activate = not CuraApplication.getInstance().getMachineManager().activeMachine activate = not CuraApplication.getInstance().getMachineManager().activeMachine
if self._createMachineFromDiscoveredDevice(device.getId(), activate = activate): if self._createMachineFromDiscoveredDevice(output_device.getId(), activate = activate):
new_devices_added.append(device) new_devices_added.append(output_device)
message.setProgress(None) message.finalize(new_devices_added, new_output_devices)
max_disp_devices = 3 @staticmethod
if len(new_devices_added) > max_disp_devices: def _updateOnlinePrinters(printer_responses: Dict[str, CloudClusterResponse]) -> None:
num_hidden = len(new_devices_added) - max_disp_devices
device_name_list = ["<li>{} ({})</li>".format(device.name, device.printerTypeName) for device in new_devices[0:max_disp_devices]]
device_name_list.append("<li>" + self.i18n_catalog.i18ncp("info:{0} gets replaced by a number of printers", "... and {0} other", "... and {0} others", num_hidden) + "</li>")
device_names = "".join(device_name_list)
else:
device_names = "".join(["<li>{} ({})</li>".format(device.name, device.printerTypeName) for device in new_devices_added])
if new_devices_added:
message_text = self.i18n_catalog.i18nc("info:status", "Printers added from Digital Factory:") + "<ul>" + device_names + "</ul>"
message.setText(message_text)
else:
message.hide()
def _updateOnlinePrinters(self, printer_responses: Dict[str, CloudClusterResponse]) -> None:
""" """
Update the metadata of the printers to store whether they are online or not. Update the metadata of the printers to store whether they are online or not.
:param printer_responses: The responses received from the API about the printer statuses. :param printer_responses: The responses received from the API about the printer statuses.
@ -291,7 +283,8 @@ class CloudOutputDeviceManager:
old_cluster_id = outdated_machine.getMetaDataEntry(self.META_CLUSTER_ID) old_cluster_id = outdated_machine.getMetaDataEntry(self.META_CLUSTER_ID)
outdated_machine.setMetaDataEntry(self.META_CLUSTER_ID, new_cloud_output_device.key) outdated_machine.setMetaDataEntry(self.META_CLUSTER_ID, new_cloud_output_device.key)
outdated_machine.setMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, True) outdated_machine.setMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, True)
# Cleanup the remainings of the old CloudOutputDevice(old_cluster_id)
# Cleanup the remains of the old CloudOutputDevice(old_cluster_id)
self._um_cloud_printers[new_cloud_output_device.key] = self._um_cloud_printers.pop(old_cluster_id) self._um_cloud_printers[new_cloud_output_device.key] = self._um_cloud_printers.pop(old_cluster_id)
output_device_manager = CuraApplication.getInstance().getOutputDeviceManager() output_device_manager = CuraApplication.getInstance().getOutputDeviceManager()
if old_cluster_id in output_device_manager.getOutputDeviceIds(): if old_cluster_id in output_device_manager.getOutputDeviceIds():
@ -321,56 +314,19 @@ class CloudOutputDeviceManager:
for device_id in removed_device_ids: for device_id in removed_device_ids:
if not parseBool(self._um_cloud_printers[device_id].getMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, "true")): if not parseBool(self._um_cloud_printers[device_id].getMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, "true")):
ignored_device_ids.add(device_id) ignored_device_ids.add(device_id)
# Keep the reported_device_ids list in a class variable, so that the message button actions can access it and # Keep the reported_device_ids list in a class variable, so that the message button actions can access it and
# take the necessary steps to fulfill their purpose. # take the necessary steps to fulfill their purpose.
self.reported_device_ids = removed_device_ids - ignored_device_ids self.reported_device_ids = removed_device_ids - ignored_device_ids
if not self.reported_device_ids: if not self.reported_device_ids:
return return
# Generate message
self._removed_printers_message = Message(
title = self.i18n_catalog.i18ncp(
"info:status",
"A cloud connection is not available for a printer",
"A cloud connection is not available for some printers",
len(self.reported_device_ids)
),
message_type = Message.MessageType.WARNING
)
device_names = "".join(["<li>{} ({})</li>".format(self._um_cloud_printers[device].name, self._um_cloud_printers[device].definition.name) for device in self.reported_device_ids])
message_text = self.i18n_catalog.i18ncp(
"info:status",
"This printer is not linked to the Digital Factory:",
"These printers are not linked to the Digital Factory:",
len(self.reported_device_ids)
)
message_text += "<br/><ul>{}</ul><br/>".format(device_names)
digital_factory_string = self.i18n_catalog.i18nc("info:name", "Ultimaker Digital Factory")
message_text += self.i18n_catalog.i18nc(
"info:status",
"To establish a connection, please visit the {website_link}".format(website_link = "<a href='https://digitalfactory.ultimaker.com?utm_source=cura&utm_medium=software&utm_campaign=change-account-connect-printer'>{}</a>.".format(digital_factory_string))
)
self._removed_printers_message.setText(message_text)
self._removed_printers_message.addAction("keep_printer_configurations_action",
name = self.i18n_catalog.i18nc("@action:button", "Keep printer configurations"),
icon = "",
description = "Keep cloud printers in Ultimaker Cura when not connected to your account.",
button_align = Message.ActionButtonAlignment.ALIGN_RIGHT)
self._removed_printers_message.addAction("remove_printers_action",
name = self.i18n_catalog.i18nc("@action:button", "Remove printers"),
icon = "",
description = "Remove cloud printer(s) which aren't linked to your account.",
button_style = Message.ActionButtonStyle.SECONDARY,
button_align = Message.ActionButtonAlignment.ALIGN_LEFT)
self._removed_printers_message.actionTriggered.connect(self._onRemovedPrintersMessageActionTriggered)
output_device_manager = CuraApplication.getInstance().getOutputDeviceManager() output_device_manager = CuraApplication.getInstance().getOutputDeviceManager()
# Remove the output device from the printers # Remove the output device from the printers
for device_id in removed_device_ids: for device_id in removed_device_ids:
device = self._um_cloud_printers.get(device_id, None) # type: Optional[GlobalStack] global_stack: Optional[GlobalStack] = self._um_cloud_printers.get(device_id, None)
if not device: if not global_stack:
continue continue
if device_id in output_device_manager.getOutputDeviceIds(): if device_id in output_device_manager.getOutputDeviceIds():
output_device_manager.removeOutputDevice(device_id) output_device_manager.removeOutputDevice(device_id)
@ -378,12 +334,19 @@ class CloudOutputDeviceManager:
del self._remote_clusters[device_id] del self._remote_clusters[device_id]
# Update the printer's metadata to mark it as not linked to the account # Update the printer's metadata to mark it as not linked to the account
device.setMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, False) global_stack.setMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, False)
# Generate message to show
device_names = "".join(["<li>{} ({})</li>".format(self._um_cloud_printers[device].name,
self._um_cloud_printers[device].definition.name) for device in
self.reported_device_ids])
self._removed_printers_message = RemovedPrintersMessage(self.reported_device_ids, device_names)
self._removed_printers_message.actionTriggered.connect(self._onRemovedPrintersMessageActionTriggered)
self._removed_printers_message.show() self._removed_printers_message.show()
def _onDiscoveredDeviceRemoved(self, device_id: str) -> None: def _onDiscoveredDeviceRemoved(self, device_id: str) -> None:
device = self._remote_clusters.pop(device_id, None) # type: Optional[CloudOutputDevice] """ Remove the CloudOutputDevices for printers that are offline"""
device: Optional[CloudOutputDevice] = self._remote_clusters.pop(device_id, None)
if not device: if not device:
return return
device.close() device.close()
@ -392,12 +355,12 @@ class CloudOutputDeviceManager:
output_device_manager.removeOutputDevice(device.key) output_device_manager.removeOutputDevice(device.key)
def _createMachineFromDiscoveredDevice(self, key: str, activate: bool = True) -> bool: def _createMachineFromDiscoveredDevice(self, key: str, activate: bool = True) -> bool:
device = self._remote_clusters[key] device = self._remote_clusters.get(key)
if not device: if not device:
return False return False
# Create a new machine. # Create a new machine.
# We do not use use MachineManager.addMachine here because we need to set the cluster ID before activating it. # We do not use MachineManager.addMachine here because we need to set the cluster ID before activating it.
new_machine = CuraStackBuilder.createMachine(device.name, device.printerType, show_warning_message=False) new_machine = CuraStackBuilder.createMachine(device.name, device.printerType, show_warning_message=False)
if not new_machine: if not new_machine:
Logger.error(f"Failed creating a new machine for {device.name}") Logger.error(f"Failed creating a new machine for {device.name}")
@ -405,8 +368,6 @@ class CloudOutputDeviceManager:
self._setOutputDeviceMetadata(device, new_machine) self._setOutputDeviceMetadata(device, new_machine)
_abstract_machine = CuraStackBuilder.createAbstractMachine(device.printerType)
if activate: if activate:
CuraApplication.getInstance().getMachineManager().setActiveMachine(new_machine.getId()) CuraApplication.getInstance().getMachineManager().setActiveMachine(new_machine.getId())
@ -414,15 +375,19 @@ class CloudOutputDeviceManager:
def _connectToActiveMachine(self) -> None: def _connectToActiveMachine(self) -> None:
"""Callback for when the active machine was changed by the user""" """Callback for when the active machine was changed by the user"""
active_machine = CuraApplication.getInstance().getGlobalContainerStack() active_machine = CuraApplication.getInstance().getGlobalContainerStack()
if not active_machine: if not active_machine:
return return
# Check if we should directly connect with a "normal" CloudOutputDevice or that we should connect to an
# 'abstract' one
output_device_manager = CuraApplication.getInstance().getOutputDeviceManager() output_device_manager = CuraApplication.getInstance().getOutputDeviceManager()
stored_cluster_id = active_machine.getMetaDataEntry(self.META_CLUSTER_ID) stored_cluster_id = active_machine.getMetaDataEntry(self.META_CLUSTER_ID)
local_network_key = active_machine.getMetaDataEntry(self.META_NETWORK_KEY) local_network_key = active_machine.getMetaDataEntry(self.META_NETWORK_KEY)
for device in list(self._remote_clusters.values()): # Make a copy of the remote devices list, to prevent modifying the list while iterating, if a device gets added asynchronously.
# Copy of the device list, to prevent modifying the list while iterating, if a device gets added asynchronously.
remote_cluster_copy: List[CloudOutputDevice] = list(self._remote_clusters.values())
for device in remote_cluster_copy:
if device.key == stored_cluster_id: if device.key == stored_cluster_id:
# Connect to it if the stored ID matches. # Connect to it if the stored ID matches.
self._connectToOutputDevice(device, active_machine) self._connectToOutputDevice(device, active_machine)
@ -433,6 +398,14 @@ class CloudOutputDeviceManager:
# Remove device if it is not meant for the active machine. # Remove device if it is not meant for the active machine.
output_device_manager.removeOutputDevice(device.key) output_device_manager.removeOutputDevice(device.key)
# Update state of all abstract output devices
remote_abstract_cluster_copy: List[CloudOutputDevice] = list(self._abstract_clusters.values())
for device in remote_abstract_cluster_copy:
if device.printerType == active_machine.definition.getId() and parseBool(active_machine.getMetaDataEntry("is_abstract_machine", False)):
self._connectToAbstractOutputDevice(device, active_machine)
elif device.key in output_device_manager.getOutputDeviceIds():
output_device_manager.removeOutputDevice(device.key)
def _setOutputDeviceMetadata(self, device: CloudOutputDevice, machine: GlobalStack): def _setOutputDeviceMetadata(self, device: CloudOutputDevice, machine: GlobalStack):
machine.setName(device.name) machine.setName(device.name)
machine.setMetaDataEntry(self.META_CLUSTER_ID, device.key) machine.setMetaDataEntry(self.META_CLUSTER_ID, device.key)
@ -440,13 +413,24 @@ class CloudOutputDeviceManager:
machine.setMetaDataEntry("group_name", device.name) machine.setMetaDataEntry("group_name", device.name)
machine.setMetaDataEntry("group_size", device.clusterSize) machine.setMetaDataEntry("group_size", device.clusterSize)
digital_factory_string = self.i18n_catalog.i18nc("info:name", "Ultimaker Digital Factory") digital_factory_string = self.i18n_catalog.i18nc("info:name", "Ultimaker Digital Factory")
digital_factory_link = "<a href='https://digitalfactory.ultimaker.com?utm_source=cura&utm_medium=software&utm_campaign=change-account-remove-printer'>{digital_factory_string}</a>".format(digital_factory_string = digital_factory_string) digital_factory_link = f"<a href='https://digitalfactory.ultimaker.com?utm_source=cura&utm_medium=software&" \
f"utm_campaign=change-account-remove-printer'>{digital_factory_string}</a>"
removal_warning_string = self.i18n_catalog.i18nc("@message {printer_name} is replaced with the name of the printer", "{printer_name} will be removed until the next account sync.").format(printer_name = device.name) \ removal_warning_string = self.i18n_catalog.i18nc("@message {printer_name} is replaced with the name of the printer", "{printer_name} will be removed until the next account sync.").format(printer_name = device.name) \
+ "<br>" + self.i18n_catalog.i18nc("@message {printer_name} is replaced with the name of the printer", "To remove {printer_name} permanently, visit {digital_factory_link}").format(printer_name = device.name, digital_factory_link = digital_factory_link) \ + "<br>" + self.i18n_catalog.i18nc("@message {printer_name} is replaced with the name of the printer", "To remove {printer_name} permanently, visit {digital_factory_link}").format(printer_name = device.name, digital_factory_link = digital_factory_link) \
+ "<br><br>" + self.i18n_catalog.i18nc("@message {printer_name} is replaced with the name of the printer", "Are you sure you want to remove {printer_name} temporarily?").format(printer_name = device.name) + "<br><br>" + self.i18n_catalog.i18nc("@message {printer_name} is replaced with the name of the printer", "Are you sure you want to remove {printer_name} temporarily?").format(printer_name = device.name)
machine.setMetaDataEntry("removal_warning", removal_warning_string) machine.setMetaDataEntry("removal_warning", removal_warning_string)
machine.addConfiguredConnectionType(device.connectionType.value) machine.addConfiguredConnectionType(device.connectionType.value)
def _connectToAbstractOutputDevice(self, device: AbstractCloudOutputDevice, machine: GlobalStack) -> None:
Logger.debug(f"Attempting to connect to abstract machine {machine.id}")
if not device.isConnected():
device.connect()
machine.addConfiguredConnectionType(device.connectionType.value)
output_device_manager = CuraApplication.getInstance().getOutputDeviceManager()
if device.key not in output_device_manager.getOutputDeviceIds():
output_device_manager.addOutputDevice(device)
def _connectToOutputDevice(self, device: CloudOutputDevice, machine: GlobalStack) -> None: def _connectToOutputDevice(self, device: CloudOutputDevice, machine: GlobalStack) -> None:
"""Connects to an output device and makes sure it is registered in the output device manager.""" """Connects to an output device and makes sure it is registered in the output device manager."""
@ -472,7 +456,7 @@ class CloudOutputDeviceManager:
if container_cluster_id in self._remote_clusters.keys(): if container_cluster_id in self._remote_clusters.keys():
del self._remote_clusters[container_cluster_id] del self._remote_clusters[container_cluster_id]
def _onRemovedPrintersMessageActionTriggered(self, removed_printers_message: Message, action: str) -> None: def _onRemovedPrintersMessageActionTriggered(self, removed_printers_message: RemovedPrintersMessage, action: str) -> None:
if action == "keep_printer_configurations_action": if action == "keep_printer_configurations_action":
removed_printers_message.hide() removed_printers_message.hide()
elif action == "remove_printers_action": elif action == "remove_printers_action":
@ -483,12 +467,16 @@ class CloudOutputDeviceManager:
question_title = self.i18n_catalog.i18nc("@title:window", "Remove printers?") question_title = self.i18n_catalog.i18nc("@title:window", "Remove printers?")
question_content = self.i18n_catalog.i18ncp( question_content = self.i18n_catalog.i18ncp(
"@label", "@label",
"You are about to remove {0} printer from Cura. This action cannot be undone.\nAre you sure you want to continue?", "You are about to remove {0} printer from Cura. This action cannot be undone.\n"
"You are about to remove {0} printers from Cura. This action cannot be undone.\nAre you sure you want to continue?", "Are you sure you want to continue?",
"You are about to remove {0} printers from Cura. This action cannot be undone.\n"
"Are you sure you want to continue?",
len(remove_printers_ids) len(remove_printers_ids)
) )
if remove_printers_ids == all_ids: if remove_printers_ids == all_ids:
question_content = self.i18n_catalog.i18nc("@label", "You are about to remove all printers from Cura. This action cannot be undone.\nAre you sure you want to continue?") question_content = self.i18n_catalog.i18nc("@label", "You are about to remove all printers from Cura. "
"This action cannot be undone.\n"
"Are you sure you want to continue?")
result = QMessageBox.question(None, question_title, question_content) result = QMessageBox.question(None, question_title, question_content)
if result == QMessageBox.StandardButton.No: if result == QMessageBox.StandardButton.No:
return return

View File

@ -0,0 +1,60 @@
# Copyright (c) 2022 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM import i18nCatalog
from UM.Message import Message
from cura.CuraApplication import CuraApplication
class NewPrinterDetectedMessage(Message):
i18n_catalog = i18nCatalog("cura")
def __init__(self, num_printers_found: int) -> None:
super().__init__(title = self.i18n_catalog.i18ncp("info:status",
"New printer detected from your Ultimaker account",
"New printers detected from your Ultimaker account",
num_printers_found),
progress = 0,
lifetime = 0,
message_type = Message.MessageType.POSITIVE)
self._printers_added = 0
self._num_printers_found = num_printers_found
def updateProgressText(self, output_device):
"""
While the progress of adding printers is running, update the text displayed.
:param output_device: The output device that is being added.
:return:
"""
message_text = self.i18n_catalog.i18nc("info:status Filled in with printer name and printer model.",
"Adding printer {name} ({model}) from your account").format(
name=output_device.name, model=output_device.printerTypeName)
self.setText(message_text)
if self._num_printers_found > 1:
self.setProgress((self._printers_added / self._num_printers_found) * 100)
self._printers_added += 1
CuraApplication.getInstance().processEvents()
def finalize(self, new_devices_added, new_output_devices):
self.setProgress(None)
num_devices_added = len(new_devices_added)
max_disp_devices = 3
if num_devices_added > max_disp_devices:
num_hidden = num_devices_added - max_disp_devices
device_name_list = ["<li>{} ({})</li>".format(device.name, device.printerTypeName) for device in
new_output_devices[0: max_disp_devices]]
device_name_list.append(
"<li>" + self.i18n_catalog.i18ncp("info:{0} gets replaced by a number of printers", "... and {0} other",
"... and {0} others", num_hidden) + "</li>")
device_names = "".join(device_name_list)
else:
device_names = "".join(
["<li>{} ({})</li>".format(device.name, device.printerTypeName) for device in new_devices_added])
if new_devices_added:
message_text = self.i18n_catalog.i18nc("info:status",
"Printers added from Digital Factory:") + f"<ul>{device_names}</ul>"
self.setText(message_text)
else:
self.hide()

View File

@ -0,0 +1,38 @@
# Copyright (c) 2022 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt6.QtCore import QUrl
from PyQt6.QtGui import QDesktopServices
from UM import i18nCatalog
from UM.Message import Message
I18N_CATALOG = i18nCatalog("cura")
class PrintJobPendingApprovalMessage(Message):
"""Message shown when waiting for approval on an uploaded print job."""
def __init__(self, cluster_id: str) -> None:
super().__init__(
text = I18N_CATALOG.i18nc("@info:status", "You will receive a confirmation via email when the print job is approved"),
title=I18N_CATALOG.i18nc("@info:title", "The print job was successfully submitted"),
message_type=Message.MessageType.POSITIVE
)
self.addAction("manage_print_jobs", I18N_CATALOG.i18nc("@action", "Manage print jobs"), "", "")
self.addAction("learn_more", I18N_CATALOG.i18nc("@action", "Learn more"), "", "",
button_style = Message.ActionButtonStyle.LINK,
button_align = Message.ActionButtonAlignment.ALIGN_LEFT)
self.actionTriggered.connect(self._onActionTriggered)
self.cluster_id = cluster_id
def _onActionTriggered(self, message: Message, action: str) -> None:
""" Callback function for the "Manage print jobs" button on the pending approval notification. """
match action:
case "manage_print_jobs":
QDesktopServices.openUrl(QUrl(f"https://digitalfactory.ultimaker.com/app/jobs/{self._cluster.cluster_id}?utm_source=cura&utm_medium=software&utm_campaign=message-printjob-sent"))
case "learn_more":
QDesktopServices.openUrl(QUrl("https://support.ultimaker.com/hc/en-us/articles/5329940078620?utm_source=cura&utm_medium=software&utm_campaign=message-printjob-sent"))

View File

@ -0,0 +1,52 @@
# Copyright (c) 2022 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM import i18nCatalog
from UM.Message import Message
from cura.CuraApplication import CuraApplication
class RemovedPrintersMessage(Message):
i18n_catalog = i18nCatalog("cura")
def __init__(self, removed_devices, device_names) -> None:
self._removed_devices = removed_devices
message_text = self.i18n_catalog.i18ncp(
"info:status",
"This printer is not linked to the Digital Factory:",
"These printers are not linked to the Digital Factory:",
len(self._removed_devices)
)
message_text += "<br/><ul>{}</ul><br/>".format(device_names)
digital_factory_string = self.i18n_catalog.i18nc("info:name", "Ultimaker Digital Factory")
website_link = f"<a href='https://digitalfactory.ultimaker.com?utm_source=cura&" \
f"utm_medium=software&utm_campaign=change-account-connect-printer'>{digital_factory_string}</a>."
message_text += self.i18n_catalog.i18nc(
"info:status",
f"To establish a connection, please visit the {website_link}"
)
super().__init__(title=self.i18n_catalog.i18ncp("info:status",
"A cloud connection is not available for a printer",
"A cloud connection is not available for some printers",
len(self.removed_devices)),
message_type=Message.MessageType.WARNING,
text = message_text)
self.addAction("keep_printer_configurations_action",
name=self.i18n_catalog.i18nc("@action:button",
"Keep printer configurations"),
icon="",
description="Keep cloud printers in Ultimaker Cura when not connected to your account.",
button_align=Message.ActionButtonAlignment.ALIGN_RIGHT)
self.addAction("remove_printers_action",
name=self.i18n_catalog.i18nc("@action:button", "Remove printers"),
icon="",
description="Remove cloud printer(s) which aren't linked to your account.",
button_style=Message.ActionButtonStyle.SECONDARY,
button_align=Message.ActionButtonAlignment.ALIGN_LEFT)

View File

@ -8,7 +8,6 @@ from ..BaseModel import BaseModel
class CloudClusterResponse(BaseModel): class CloudClusterResponse(BaseModel):
"""Class representing a cloud connected cluster.""" """Class representing a cloud connected cluster."""
def __init__(self, cluster_id: str, host_guid: str, host_name: str, is_online: bool, status: str, def __init__(self, cluster_id: str, host_guid: str, host_name: str, is_online: bool, status: str,
host_internal_ip: Optional[str] = None, host_version: Optional[str] = None, host_internal_ip: Optional[str] = None, host_version: Optional[str] = None,
friendly_name: Optional[str] = None, printer_type: str = "ultimaker3", printer_count: int = 1, friendly_name: Optional[str] = None, printer_type: str = "ultimaker3", printer_count: int = 1,

View File

@ -0,0 +1,14 @@
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional, List
from .CloudClusterResponse import CloudClusterResponse
from .ClusterPrinterStatus import ClusterPrinterStatus
class CloudClusterWithConfigResponse(CloudClusterResponse):
"""Class representing a cloud connected cluster."""
def __init__(self, **kwargs) -> None:
self.configuration = self.parseModel(ClusterPrinterStatus, kwargs.get("host_printer"))
super().__init__(**kwargs)

View File

@ -20,7 +20,6 @@ from ..BaseModel import BaseModel
class ClusterPrinterStatus(BaseModel): class ClusterPrinterStatus(BaseModel):
"""Class representing a cluster printer""" """Class representing a cluster printer"""
def __init__(self, enabled: bool, firmware_version: str, friendly_name: str, ip_address: str, machine_variant: str, def __init__(self, enabled: bool, firmware_version: str, friendly_name: str, ip_address: str, machine_variant: str,
status: str, unique_name: str, uuid: str, status: str, unique_name: str, uuid: str,
configuration: List[Union[Dict[str, Any], ClusterPrintCoreConfiguration]], configuration: List[Union[Dict[str, Any], ClusterPrintCoreConfiguration]],
@ -28,9 +27,9 @@ class ClusterPrinterStatus(BaseModel):
firmware_update_status: Optional[str] = None, latest_available_firmware: Optional[str] = None, firmware_update_status: Optional[str] = None, latest_available_firmware: Optional[str] = None,
build_plate: Union[Dict[str, Any], ClusterBuildPlate] = None, build_plate: Union[Dict[str, Any], ClusterBuildPlate] = None,
material_station: Union[Dict[str, Any], ClusterPrinterMaterialStation] = None, **kwargs) -> None: material_station: Union[Dict[str, Any], ClusterPrinterMaterialStation] = None, **kwargs) -> None:
"""Creates a new cluster printer status """
Creates a new cluster printer status
:param enabled: A printer can be disabled if it should not receive new jobs. By default every printer is enabled. :param enabled: A printer can be disabled if it should not receive new jobs. By default, every printer is enabled.
:param firmware_version: Firmware version installed on the printer. Can differ for each printer in a cluster. :param firmware_version: Firmware version installed on the printer. Can differ for each printer in a cluster.
:param friendly_name: Human readable name of the printer. Can be used for identification purposes. :param friendly_name: Human readable name of the printer. Can be used for identification purposes.
:param ip_address: The IP address of the printer in the local network. :param ip_address: The IP address of the printer in the local network.

View File

@ -221,7 +221,7 @@ class LocalClusterOutputDeviceManager:
return return
# Create a new machine and activate it. # Create a new machine and activate it.
# We do not use use MachineManager.addMachine here because we need to set the network key before activating it. # We do not use MachineManager.addMachine here because we need to set the network key before activating it.
# If we do not do this the auto-pairing with the cloud-equivalent device will not work. # If we do not do this the auto-pairing with the cloud-equivalent device will not work.
new_machine = CuraStackBuilder.createMachine(device.name, device.printerType) new_machine = CuraStackBuilder.createMachine(device.name, device.printerType)
if not new_machine: if not new_machine:
@ -234,7 +234,6 @@ class LocalClusterOutputDeviceManager:
_abstract_machine = CuraStackBuilder.createAbstractMachine(device.printerType) _abstract_machine = CuraStackBuilder.createAbstractMachine(device.printerType)
def _storeManualAddress(self, address: str) -> None: def _storeManualAddress(self, address: str) -> None:
"""Add an address to the stored preferences.""" """Add an address to the stored preferences."""

View File

@ -28,7 +28,7 @@
"retraction_speed": { "default_value": 50}, "retraction_speed": { "default_value": 50},
"gantry_height": { "value": "30" }, "gantry_height": { "value": "30" },
"speed_print": { "default_value": 50 }, "speed_print": { "default_value": 50 },
"material_print_temperature": { "value": 195 }, "default_material_print_temperature": { "value": 195 },
"material_print_temperature_layer_0": { "value": "material_print_temperature" }, "material_print_temperature_layer_0": { "value": "material_print_temperature" },
"material_initial_print_temperature": { "value": "material_print_temperature" }, "material_initial_print_temperature": { "value": "material_print_temperature" },
"material_final_print_temperature": { "value": 195 }, "material_final_print_temperature": { "value": 195 },
@ -41,11 +41,9 @@
"machine_max_acceleration_z": { "value": 100 }, "machine_max_acceleration_z": { "value": 100 },
"machine_max_acceleration_e": { "value": 500 }, "machine_max_acceleration_e": { "value": 500 },
"machine_acceleration": { "value": 500 }, "machine_acceleration": { "value": 500 },
"machine_max_jerk_xy": { "value": 8 },
"machine_max_jerk_z": { "value": 0.4 },
"machine_max_jerk_e": { "value": 5 },
"material_diameter": { "default_value": 1.75 }, "material_diameter": { "default_value": 1.75 },
"infill_overlap": { "default_value": 15 },
"acceleration_print": { "value": 500 }, "acceleration_print": { "value": 500 },
"acceleration_travel": { "value": 500 }, "acceleration_travel": { "value": 500 },
"acceleration_travel_layer_0": { "value": "acceleration_travel" }, "acceleration_travel_layer_0": { "value": "acceleration_travel" },

View File

@ -36,9 +36,8 @@
"min_infill_area": { "default_value": 2.0 }, "min_infill_area": { "default_value": 2.0 },
"retract_at_layer_change": { "default_value": true }, "retract_at_layer_change": { "default_value": true },
"default_material_print_temperature": { "default_value": 210 }, "default_material_print_temperature": { "default_value": 210 },
"material_print_temperature": { "value": 210 },
"material_final_print_temperature": { "value": 210 }, "material_final_print_temperature": { "value": 210 },
"material_bed_temperature": { "value": 70 }, "default_material_bed_temperature": { "value": 70 },
"material_bed_temperature_layer_0": { "value": 70 }, "material_bed_temperature_layer_0": { "value": 70 },
"material_flow_layer_0": {"value": 140}, "material_flow_layer_0": {"value": 140},
"retraction_amount": { "default_value": 10 }, "retraction_amount": { "default_value": 10 },

View File

@ -1,17 +1,14 @@
{ {
"name": "Creality Ender-3 Pro", "name": "Creality Ender-6",
"version": 2, "version": 2,
"inherits": "creality_base", "inherits": "creality_base",
"metadata": {
"quality_definition": "creality_base",
"visible": true,
"platform": "creality_ender3.3mf"
},
"overrides": { "overrides": {
"machine_name": { "default_value": "Creality Ender-3 Pro" }, "machine_name": { "default_value": "Creality Ender-6" },
"machine_width": { "default_value": 220 }, "machine_start_gcode": { "default_value": "\nG28 ;Home\n\nG92 E0 ;Reset Extruder\nG1 Z2.0 F3000 ;Move Z Axis up\nG1 X10.1 Y20 Z0.28 F5000.0 ;Move to start position\nG1 X10.1 Y200.0 Z0.28 F1500.0 E15 ;Draw the first line\nG1 X10.4 Y200.0 Z0.28 F5000.0 ;Move to side a little\nG1 X10.4 Y20 Z0.28 F1500.0 E30 ;Draw the second line\nG92 E0 ;Reset Extruder\nG1 Z2.0 F3000 ;Move Z Axis up\n"},
"machine_depth": { "default_value": 220 }, "machine_end_gcode": { "default_value": "G91 ;Relative positioning\nG1 E-2 F2700 ;Retract a bit\nG1 E-2 Z0.2 F2400 ;Retract and raise Z\nG1 X5 Y5 F3000 ;Wipe out\nG1 Z10 ;Raise Z more\nG90 ;Absolute positioning\n\nG28 X Y ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\n\nM84 X Y E ;Disable all steppers but Z\n" },
"machine_height": { "default_value": 250 }, "machine_width": { "default_value": 260 },
"machine_depth": { "default_value": 260 },
"machine_height": { "default_value": 400 },
"machine_head_with_fans_polygon": { "default_value": [ "machine_head_with_fans_polygon": { "default_value": [
[-26, 34], [-26, 34],
[-26, -32], [-26, -32],
@ -19,10 +16,14 @@
[32, 34] [32, 34]
] ]
}, },
"machine_start_gcode": {
"default_value": "; Ender 3 Custom Start G-code\nG92 E0 ; Reset Extruder\nG28 ; Home all axes\nM104 S{material_standby_temperature} ; Start heating up the nozzle most of the way\nM190 S{material_bed_temperature_layer_0} ; Start heating the bed, wait until target temperature reached\nM109 S{material_print_temperature_layer_0} ; Finish heating the nozzle\nG1 Z2.0 F3000 ; Move Z Axis up little to prevent scratching of Heat Bed\nG1 X0.1 Y20 Z0.3 F5000.0 ; Move to start position\nG1 X0.1 Y200.0 Z0.3 F1500.0 E15 ; Draw the first line\nG1 X0.4 Y200.0 Z0.3 F5000.0 ; Move to side a little\nG1 X0.4 Y20 Z0.3 F1500.0 E30 ; Draw the second line\nG92 E0 ; Reset Extruder\nG1 Z2.0 F3000 ; Move Z Axis up little to prevent scratching of Heat Bed\nG1 X5 Y20 Z0.3 F5000.0 ; Move over to prevent blob squish"
},
"gantry_height": { "value": 25 } "gantry_height": { "value": 25 },
"speed_print": { "value": 80.0 }
},
"metadata": {
"quality_definition": "creality_base",
"visible": true
} }
} }

View File

@ -24,7 +24,6 @@
"xy_offset": { "value": 0.1 }, "xy_offset": { "value": 0.1 },
"xy_offset_layer_0": { "value": -0.1 }, "xy_offset_layer_0": { "value": -0.1 },
"hole_xy_offset": { "value": 0.15 }, "hole_xy_offset": { "value": 0.15 },
"material_print_temperature": { "value": 200 },
"speed_travel": { "value": 100 }, "speed_travel": { "value": 100 },
"speed_layer_0": { "value": 25 }, "speed_layer_0": { "value": 25 },
"acceleration_enabled": { "value": true }, "acceleration_enabled": { "value": true },

View File

@ -53,7 +53,7 @@
"machine_end_gcode": { "machine_end_gcode": {
"default_value": "G91 ;Relative positioning\nG1 Z5 F720 ;Raise Z\nG1 E-5 F300 ;Retract a bit to protect nozzle\nM104 S0 ;Turn off extruder\nM140 S0 ;Turn off bed\nM107 ;Turn off all fans\nG90 ;Absolute positioning\nG1 X230 Y200 F4800 ;Parking the hotend\nM84 X Y E ;All steppers off but left Z\n" "default_value": "G91 ;Relative positioning\nG1 Z5 F720 ;Raise Z\nG1 E-5 F300 ;Retract a bit to protect nozzle\nM104 S0 ;Turn off extruder\nM140 S0 ;Turn off bed\nM107 ;Turn off all fans\nG90 ;Absolute positioning\nG1 X230 Y200 F4800 ;Parking the hotend\nM84 X Y E ;All steppers off but left Z\n"
}, },
"material_print_temperature": { "default_material_print_temperature": {
"value": 205 "value": 205
}, },
"material_print_temperature_layer_0": { "material_print_temperature_layer_0": {
@ -103,7 +103,7 @@
"infill_before_walls": { "infill_before_walls": {
"value": false "value": false
}, },
"material_bed_temperature": { "default_material_bed_temperature": {
"value": 60 "value": 60
}, },
"material_bed_temperature_layer_0": { "material_bed_temperature_layer_0": {

View File

@ -138,7 +138,6 @@
"value": "material_print_temperature" "value": "material_print_temperature"
}, },
"material_bed_temperature": { "material_bed_temperature": {
"value": "default_material_bed_temperature",
"maximum_value_warning": 100 "maximum_value_warning": 100
}, },
"material_bed_temperature_layer_0": { "material_bed_temperature_layer_0": {

View File

@ -1443,7 +1443,63 @@
"value": "0", "value": "0",
"limit_to_extruder": "roofing_extruder_nr", "limit_to_extruder": "roofing_extruder_nr",
"settable_per_mesh": true, "settable_per_mesh": true,
"enabled": "top_layers > 0" "enabled": "top_layers > 0",
"children": {
"roofing_line_width":
{
"label": "Top Surface Skin Line Width",
"description": "Width of a single line of the areas at the top of the print.",
"unit": "mm",
"minimum_value": "0.001",
"minimum_value_warning": "0.1 + 0.4 * machine_nozzle_size",
"maximum_value_warning": "2 * machine_nozzle_size",
"default_value": 0.4,
"type": "float",
"value": "skin_line_width",
"limit_to_extruder": "roofing_extruder_nr",
"settable_per_mesh": true,
"enabled": "roofing_layer_count > 0 and top_layers > 0"
},
"roofing_pattern":
{
"label": "Top Surface Skin Pattern",
"description": "The pattern of the top most layers.",
"type": "enum",
"options":
{
"lines": "Lines",
"concentric": "Concentric",
"zigzag": "Zig Zag"
},
"default_value": "lines",
"value": "top_bottom_pattern",
"limit_to_extruder": "roofing_extruder_nr",
"settable_per_mesh": true,
"enabled": "roofing_layer_count > 0 and top_layers > 0"
},
"roofing_monotonic":
{
"label": "Monotonic Top Surface Order",
"description": "Print top surface lines in an ordering that causes them to always overlap with adjacent lines in a single direction. This takes slightly more time to print, but makes flat surfaces look more consistent.",
"type": "bool",
"default_value": false,
"value": "skin_monotonic",
"enabled": "roofing_layer_count > 0 and top_layers > 0 and roofing_pattern != 'concentric'",
"limit_to_extruder": "roofing_extruder_nr",
"settable_per_mesh": true
},
"roofing_angles":
{
"label": "Top Surface Skin Line Directions",
"description": "A list of integer line directions to use when the top surface skin layers use the lines or zig zag pattern. Elements from the list are used sequentially as the layers progress and when the end of the list is reached, it starts at the beginning again. The list items are separated by commas and the whole list is contained in square brackets. Default is an empty list which means use the traditional default angles (45 and 135 degrees).",
"type": "[int]",
"default_value": "[ ]",
"value": "skin_angles",
"enabled": "roofing_pattern != 'concentric' and roofing_layer_count > 0 and top_layers > 0",
"limit_to_extruder": "roofing_extruder_nr",
"settable_per_mesh": true
}
}
}, },
"top_bottom_extruder_nr": "top_bottom_extruder_nr":
{ {
@ -4482,6 +4538,20 @@
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": true "settable_per_extruder": true
}, },
"support_tree_max_diameter":
{
"label": "Tree Support Trunk Diameter",
"description": "The diameter of the widest branches of tree support. A thicker trunk is more sturdy; a thinner trunk takes up less space on the build plate.",
"unit": "mm",
"type": "float",
"minimum_value": "support_tree_branch_diameter",
"minimum_value_warning": "support_line_width * 5",
"default_value": 15,
"limit_to_extruder": "support_infill_extruder_nr",
"enabled": "support_enable and support_structure=='tree'",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"support_tree_branch_diameter_angle": "support_tree_branch_diameter_angle":
{ {
"label": "Tree Support Branch Diameter Angle", "label": "Tree Support Branch Diameter Angle",
@ -6335,6 +6405,7 @@
"description": "Remove the holes in each layer and keep only the outside shape. This will ignore any invisible internal geometry. However, it also ignores layer holes which can be viewed from above or below.", "description": "Remove the holes in each layer and keep only the outside shape. This will ignore any invisible internal geometry. However, it also ignores layer holes which can be viewed from above or below.",
"type": "bool", "type": "bool",
"default_value": false, "default_value": false,
"value": "magic_spiralize",
"settable_per_mesh": true "settable_per_mesh": true
}, },
"meshfix_extensive_stitching": "meshfix_extensive_stitching":
@ -6645,60 +6716,6 @@
"default_value": "middle", "default_value": "middle",
"settable_per_mesh": true "settable_per_mesh": true
}, },
"roofing_line_width":
{
"label": "Top Surface Skin Line Width",
"description": "Width of a single line of the areas at the top of the print.",
"unit": "mm",
"minimum_value": "0.001",
"minimum_value_warning": "0.1 + 0.4 * machine_nozzle_size",
"maximum_value_warning": "2 * machine_nozzle_size",
"default_value": 0.4,
"type": "float",
"value": "skin_line_width",
"limit_to_extruder": "roofing_extruder_nr",
"settable_per_mesh": true,
"enabled": "roofing_layer_count > 0 and top_layers > 0"
},
"roofing_pattern":
{
"label": "Top Surface Skin Pattern",
"description": "The pattern of the top most layers.",
"type": "enum",
"options":
{
"lines": "Lines",
"concentric": "Concentric",
"zigzag": "Zig Zag"
},
"default_value": "lines",
"value": "top_bottom_pattern",
"limit_to_extruder": "roofing_extruder_nr",
"settable_per_mesh": true,
"enabled": "roofing_layer_count > 0 and top_layers > 0"
},
"roofing_monotonic":
{
"label": "Monotonic Top Surface Order",
"description": "Print top surface lines in an ordering that causes them to always overlap with adjacent lines in a single direction. This takes slightly more time to print, but makes flat surfaces look more consistent.",
"type": "bool",
"default_value": false,
"value": "skin_monotonic",
"enabled": "roofing_layer_count > 0 and top_layers > 0 and roofing_pattern != 'concentric'",
"limit_to_extruder": "roofing_extruder_nr",
"settable_per_mesh": true
},
"roofing_angles":
{
"label": "Top Surface Skin Line Directions",
"description": "A list of integer line directions to use when the top surface skin layers use the lines or zig zag pattern. Elements from the list are used sequentially as the layers progress and when the end of the list is reached, it starts at the beginning again. The list items are separated by commas and the whole list is contained in square brackets. Default is an empty list which means use the traditional default angles (45 and 135 degrees).",
"type": "[int]",
"default_value": "[ ]",
"value": "skin_angles",
"enabled": "roofing_pattern != 'concentric' and roofing_layer_count > 0 and top_layers > 0",
"limit_to_extruder": "roofing_extruder_nr",
"settable_per_mesh": true
},
"infill_enable_travel_optimization": "infill_enable_travel_optimization":
{ {
"label": "Infill Travel Optimization", "label": "Infill Travel Optimization",

View File

@ -113,7 +113,6 @@
"material_initial_print_temperature": {"value": "material_print_temperature", "material_initial_print_temperature": {"value": "material_print_temperature",
"maximum_value_warning": "material_print_temperature + 15", "maximum_value_warning": "material_print_temperature + 15",
"maximum_value": "401" }, "maximum_value": "401" },
"material_initial_print_temperature": {"maximum_value": "401" },
"material_final_print_temperature": {"value": "material_print_temperature", "material_final_print_temperature": {"value": "material_print_temperature",
"maximum_value": "401" }, "maximum_value": "401" },
"material_break_preparation_temperature": {"maximum_value": "401" }, "material_break_preparation_temperature": {"maximum_value": "401" },

View File

@ -58,7 +58,7 @@
"infill_sparse_density": { "value": "15" }, "infill_sparse_density": { "value": "15" },
"infill_pattern": { "value": "'lines' if infill_sparse_density > 50 else 'cubic'" }, "infill_pattern": { "value": "'lines' if infill_sparse_density > 50 else 'cubic'" },
"material_print_temperature": { "value": "195" }, "default_material_print_temperature": { "value": "195" },
"material_print_temperature_layer_0": { "value": "material_print_temperature" }, "material_print_temperature_layer_0": { "value": "material_print_temperature" },
"material_initial_print_temperature": { "value": "material_print_temperature" }, "material_initial_print_temperature": { "value": "material_print_temperature" },
"material_final_print_temperature": { "value": "material_print_temperature" }, "material_final_print_temperature": { "value": "material_print_temperature" },

View File

@ -75,12 +75,11 @@
"infill_sparse_density": { "value": "15" }, "infill_sparse_density": { "value": "15" },
"infill_pattern": { "value": "'lines' if infill_sparse_density > 50 else 'cubic'" }, "infill_pattern": { "value": "'lines' if infill_sparse_density > 50 else 'cubic'" },
"material_print_temperature": { "value": "195" }, "default_material_print_temperature": { "value": "195" },
"material_print_temperature_layer_0": { "value": "material_print_temperature" }, "material_print_temperature_layer_0": { "value": "material_print_temperature" },
"material_initial_print_temperature": { "value": "material_print_temperature" }, "material_initial_print_temperature": { "value": "material_print_temperature" },
"material_final_print_temperature": { "value": "material_print_temperature" }, "material_final_print_temperature": { "value": "material_print_temperature" },
"material_bed_temperature": { "value": "55" }, "default_material_bed_temperature": { "value": "55" },
"material_bed_temperature_layer_0": { "value": "material_bed_temperature" },
"material_flow": { "value": 100 }, "material_flow": { "value": 100 },
"material_standby_temperature": { "value": "material_print_temperature" }, "material_standby_temperature": { "value": "material_print_temperature" },

View File

@ -77,13 +77,12 @@
"infill_sparse_density": { "value": "15" }, "infill_sparse_density": { "value": "15" },
"infill_pattern": { "value": "'lines' if infill_sparse_density > 50 else 'cubic'" }, "infill_pattern": { "value": "'lines' if infill_sparse_density > 50 else 'cubic'" },
"material_print_temperature": { "value": "195" }, "default_material_print_temperature": { "value": "195" },
"material_print_temperature_layer_0": { "value": "material_print_temperature" }, "material_print_temperature_layer_0": { "value": "material_print_temperature" },
"material_initial_print_temperature": { "value": "material_print_temperature" }, "material_initial_print_temperature": { "value": "material_print_temperature" },
"material_final_print_temperature": { "value": "material_print_temperature" }, "material_final_print_temperature": { "value": "material_print_temperature" },
"material_standby_temperature": { "value": "material_print_temperature" }, "material_standby_temperature": { "value": "material_print_temperature" },
"material_bed_temperature": { "value": "45" }, "default_material_bed_temperature": { "value": "45" },
"material_bed_temperature_layer_0": { "value": "material_bed_temperature" },
"material_flow": { "value": 100 }, "material_flow": { "value": 100 },
"speed_print": { "value": 50.0 } , "speed_print": { "value": 50.0 } ,

View File

@ -68,7 +68,6 @@
"infill_support_enabled": {"value": false }, "infill_support_enabled": {"value": false },
"max_skin_angle_for_expansion": {"value": 90}, "max_skin_angle_for_expansion": {"value": 90},
"default_material_print_temperature": {"value": 220}, "default_material_print_temperature": {"value": 220},
"material_print_temperature": {"value": 220},
"material_print_temperature_layer_0": {"value": 220}, "material_print_temperature_layer_0": {"value": 220},
"material_initial_print_temperature": {"value": 220}, "material_initial_print_temperature": {"value": 220},
"material_final_print_temperature": {"value": 220}, "material_final_print_temperature": {"value": 220},

View File

@ -104,7 +104,7 @@
"material_flow": { "material_flow": {
"value": "110" "value": "110"
}, },
"material_print_temperature": { "default_material_print_temperature": {
"value": "210.0" "value": "210.0"
}, },
"ooze_shield_enabled": { "ooze_shield_enabled": {

View File

@ -104,9 +104,6 @@
"material_flow": { "material_flow": {
"value": "110" "value": "110"
}, },
"material_print_temperature": {
"value": "210.0"
},
"ooze_shield_enabled": { "ooze_shield_enabled": {
"value": "True" "value": "True"
}, },

View File

@ -92,9 +92,6 @@
"material_bed_temperature": { "material_bed_temperature": {
"value": "70" "value": "70"
}, },
"material_print_temperature": {
"value": "210.0"
},
"ooze_shield_enabled": { "ooze_shield_enabled": {
"value": "True" "value": "True"
}, },

View File

@ -30,8 +30,11 @@
"support_skip_zag_per_mm": { "support_skip_zag_per_mm": {
"default_value": 10 "default_value": 10
}, },
"default_material_bed_temperature":
{
"value": "50"
},
"material_bed_temperature": { "material_bed_temperature": {
"value": "50",
"minimum_value_warning": "30", "minimum_value_warning": "30",
"resolve": "extruderValues('material_bed_temperature')[adhesion_extruder_nr] if resolveOrValue('adhesion_type') == 'raft' else max(extruderValues('material_bed_temperature'))" "resolve": "extruderValues('material_bed_temperature')[adhesion_extruder_nr] if resolveOrValue('adhesion_type') == 'raft' else max(extruderValues('material_bed_temperature'))"
}, },
@ -366,9 +369,6 @@
"z_seam_x": { "z_seam_x": {
"value": "115" "value": "115"
}, },
"material_print_temperature": {
"value": "195"
},
"material_bed_temperature_layer_0": { "material_bed_temperature_layer_0": {
"value": "50", "value": "50",
"minimum_value_warning": "30", "minimum_value_warning": "30",

View File

@ -24,6 +24,7 @@
"maximum_value_warning": "125" "maximum_value_warning": "125"
}, },
"material_standby_temperature": { "material_standby_temperature": {
"value": "material_print_temperature - 100",
"minimum_value": "0" "minimum_value": "0"
}, },
"extruder_prime_pos_y": "extruder_prime_pos_y":

View File

@ -109,7 +109,6 @@
"material_print_temperature_layer_0": { "value": "material_print_temperature + 5" }, "material_print_temperature_layer_0": { "value": "material_print_temperature + 5" },
"material_bed_temperature": { "maximum_value": "115" }, "material_bed_temperature": { "maximum_value": "115" },
"material_bed_temperature_layer_0": { "maximum_value": "115" }, "material_bed_temperature_layer_0": { "maximum_value": "115" },
"material_standby_temperature": { "value": "100" },
"multiple_mesh_overlap": { "value": "0" }, "multiple_mesh_overlap": { "value": "0" },
"optimize_wall_printing_order": { "value": "True" }, "optimize_wall_printing_order": { "value": "True" },
"prime_tower_enable": { "default_value": true }, "prime_tower_enable": { "default_value": true },

View File

@ -98,7 +98,6 @@
"layer_start_y": { "value": "sum(extruderValues('machine_extruder_start_pos_y')) / len(extruderValues('machine_extruder_start_pos_y'))" }, "layer_start_y": { "value": "sum(extruderValues('machine_extruder_start_pos_y')) / len(extruderValues('machine_extruder_start_pos_y'))" },
"machine_min_cool_heat_time_window": { "value": "15" }, "machine_min_cool_heat_time_window": { "value": "15" },
"default_material_print_temperature": { "value": "200" }, "default_material_print_temperature": { "value": "200" },
"material_standby_temperature": { "value": "100" },
"multiple_mesh_overlap": { "value": "0" }, "multiple_mesh_overlap": { "value": "0" },
"optimize_wall_printing_order": { "value": "True" }, "optimize_wall_printing_order": { "value": "True" },
"prime_tower_enable": { "value": "True" }, "prime_tower_enable": { "value": "True" },
@ -134,7 +133,6 @@
"switch_extruder_prime_speed": { "value": "15" }, "switch_extruder_prime_speed": { "value": "15" },
"switch_extruder_retraction_amount": { "value": "8" }, "switch_extruder_retraction_amount": { "value": "8" },
"top_bottom_thickness": { "value": "1" }, "top_bottom_thickness": { "value": "1" },
"travel_avoid_supports": { "value": "True" },
"travel_avoid_distance": { "value": "3 if extruders_enabled_count > 1 else machine_nozzle_tip_outer_diameter / 2 * 1.5" }, "travel_avoid_distance": { "value": "3 if extruders_enabled_count > 1 else machine_nozzle_tip_outer_diameter / 2 * 1.5" },
"wall_0_inset": { "value": "0" }, "wall_0_inset": { "value": "0" },
"initial_layer_line_width_factor": { "value": "120" }, "initial_layer_line_width_factor": { "value": "120" },

View File

@ -100,7 +100,6 @@
"layer_start_y": { "value": "sum(extruderValues('machine_extruder_start_pos_y')) / len(extruderValues('machine_extruder_start_pos_y'))" }, "layer_start_y": { "value": "sum(extruderValues('machine_extruder_start_pos_y')) / len(extruderValues('machine_extruder_start_pos_y'))" },
"machine_min_cool_heat_time_window": { "value": "15" }, "machine_min_cool_heat_time_window": { "value": "15" },
"default_material_print_temperature": { "value": "200" }, "default_material_print_temperature": { "value": "200" },
"material_standby_temperature": { "value": "100" },
"multiple_mesh_overlap": { "value": "0" }, "multiple_mesh_overlap": { "value": "0" },
"prime_tower_enable": { "value": "True" }, "prime_tower_enable": { "value": "True" },
"raft_airgap": { "value": "0" }, "raft_airgap": { "value": "0" },
@ -136,7 +135,6 @@
"switch_extruder_prime_speed": { "value": "15" }, "switch_extruder_prime_speed": { "value": "15" },
"switch_extruder_retraction_amount": { "value": "8" }, "switch_extruder_retraction_amount": { "value": "8" },
"top_bottom_thickness": { "value": "1" }, "top_bottom_thickness": { "value": "1" },
"travel_avoid_supports": { "value": "True" },
"travel_avoid_distance": { "value": "3 if extruders_enabled_count > 1 else machine_nozzle_tip_outer_diameter / 2 * 1.5" }, "travel_avoid_distance": { "value": "3 if extruders_enabled_count > 1 else machine_nozzle_tip_outer_diameter / 2 * 1.5" },
"wall_0_inset": { "value": "0" }, "wall_0_inset": { "value": "0" },
"optimize_wall_printing_order": { "value": "True" }, "optimize_wall_printing_order": { "value": "True" },

View File

@ -221,10 +221,7 @@ Button
} }
} }
Component.onCompleted: Component.onCompleted: configurationItem.checked = Cura.MachineManager.matchesConfiguration(configuration)
{
configurationItem.checked = Cura.MachineManager.matchesConfiguration(configuration)
}
onClicked: onClicked:
{ {

View File

@ -7,11 +7,61 @@ import QtQuick.Controls 2.3
import UM 1.5 as UM import UM 1.5 as UM
import Cura 1.0 as Cura import Cura 1.0 as Cura
Loader {
id: loader
width: parent.width
sourceComponent: {
switch (model.componentType) {
case "HIDE_BUTTON":
hideButtonComponent
break;
case "SHOW_BUTTON":
showButtonComponent
break;
case "MACHINE":
machineListButtonComponent
break;
default:
}
}
property var onClicked
Component
{
id: hideButtonComponent
Cura.TertiaryButton
{
text: catalog.i18nc("@label", "Hide all connected printers")
height: UM.Theme.getSize("large_button").height
onClicked: if (loader.onClicked) loader.onClicked()
iconSource: UM.Theme.getIcon("ChevronSingleUp")
width: parent.width
}
}
Component
{
id: showButtonComponent
Cura.TertiaryButton
{
text: catalog.i18nc("@label", "Show all connected printers")
height: UM.Theme.getSize("large_button").height
onClicked: if (loader.onClicked) loader.onClicked()
iconSource: UM.Theme.getIcon("ChevronSingleDown")
width: parent.width
}
}
Component
{
id: machineListButtonComponent
Button Button
{ {
id: machineListButton id: machineListButton
onClicked: if (loader.onClicked) loader.onClicked()
width: parent.width width: parent.width
height: UM.Theme.getSize("large_button").height height: UM.Theme.getSize("large_button").height
leftPadding: UM.Theme.getSize("default_margin").width leftPadding: UM.Theme.getSize("default_margin").width
@ -30,8 +80,8 @@ Button
height: UM.Theme.getSize("medium_button").height height: UM.Theme.getSize("medium_button").height
width: UM.Theme.getSize("medium_button").width width: UM.Theme.getSize("medium_button").width
color: UM.Theme.getColor("machine_selector_printer_icon") color: UM.Theme.getColor("machine_selector_printer_icon")
visible: model.machineType == "abstract_machine" || !model.isOnline visible: model.isAbstractMachine || !model.isOnline
source: model.machineType == "abstract_machine" ? UM.Theme.getIcon("PrinterTriple", "medium") : UM.Theme.getIcon("Printer", "medium") source: model.isAbstractMachine ? UM.Theme.getIcon("PrinterTriple", "medium") : UM.Theme.getIcon("Printer", "medium")
anchors anchors
{ {
@ -50,8 +100,8 @@ Button
verticalCenter: parent.verticalCenter verticalCenter: parent.verticalCenter
leftMargin: UM.Theme.getSize("default_margin").width leftMargin: UM.Theme.getSize("default_margin").width
} }
text: machineListButton.text text: model.name ? model.name : ""
font: model.machineType == "abstract_machine" ? UM.Theme.getFont("medium_bold") : UM.Theme.getFont("medium") font: model.isAbstractMachine ? UM.Theme.getFont("medium_bold") : UM.Theme.getFont("medium")
visible: text != "" visible: text != ""
elide: Text.ElideRight elide: Text.ElideRight
} }
@ -68,11 +118,11 @@ Button
top: buttonText.top top: buttonText.top
bottom: buttonText.bottom bottom: buttonText.bottom
} }
visible: model.machineType == "abstract_machine" visible: model.isAbstractMachine ? model.isAbstractMachine : false
UM.Label UM.Label
{ {
text: model.machineCount text: model.machineCount ? model.machineCount : ""
anchors.centerIn: parent anchors.centerIn: parent
font: UM.Theme.getFont("default_bold") font: UM.Theme.getFont("default_bold")
} }
@ -85,3 +135,5 @@ Button
color: machineListButton.hovered ? UM.Theme.getColor("action_button_hovered") : "transparent" color: machineListButton.hovered ? UM.Theme.getColor("action_button_hovered") : "transparent"
} }
} }
}
}

View File

@ -223,7 +223,6 @@ Cura.ExpandablePopup
id: buttonRow id: buttonRow
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right

View File

@ -22,8 +22,8 @@ ListView
section.delegate: UM.Label section.delegate: UM.Label
{ {
text: section == "true" ? catalog.i18nc("@label", "Connected printers") : catalog.i18nc("@label", "Other printers") text: section == "true" ? catalog.i18nc("@label", "Connected printers") : catalog.i18nc("@label", "Other printers")
width: parent.width - scrollBar.width
height: UM.Theme.getSize("action_button").height height: UM.Theme.getSize("action_button").height
width: parent.width - scrollBar.width
leftPadding: UM.Theme.getSize("default_margin").width leftPadding: UM.Theme.getSize("default_margin").width
font: UM.Theme.getFont("medium") font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("text_medium") color: UM.Theme.getColor("text_medium")
@ -31,13 +31,23 @@ ListView
delegate: MachineListButton delegate: MachineListButton
{ {
text: model.name ? model.name : ""
width: listView.width - scrollBar.width width: listView.width - scrollBar.width
onClicked: onClicked: function()
{ {
switch (model.componentType) {
case "HIDE_BUTTON":
listView.model.setShowCloudPrinters(false);
break;
case "SHOW_BUTTON":
listView.model.setShowCloudPrinters(true);
break;
case "MACHINE":
toggleContent() toggleContent()
Cura.MachineManager.setActiveMachine(model.id) Cura.MachineManager.setActiveMachine(model.id)
break;
default:
}
} }
} }
} }

View File

@ -18,7 +18,6 @@ machine_nozzle_heat_up_speed = 1.5
material_print_temperature = =default_material_print_temperature + 5 material_print_temperature = =default_material_print_temperature + 5
material_initial_print_temperature = =material_print_temperature - 5 material_initial_print_temperature = =material_print_temperature - 5
material_final_print_temperature = =material_print_temperature - 10 material_final_print_temperature = =material_print_temperature - 10
material_standby_temperature = 100
prime_tower_enable = False prime_tower_enable = False
speed_print = 60 speed_print = 60
speed_layer_0 = =math.ceil(speed_print * 20 / 60) speed_layer_0 = =math.ceil(speed_print * 20 / 60)

View File

@ -15,7 +15,6 @@ variant = AA 0.4
cool_min_speed = 12 cool_min_speed = 12
machine_nozzle_cool_down_speed = 0.8 machine_nozzle_cool_down_speed = 0.8
machine_nozzle_heat_up_speed = 1.5 machine_nozzle_heat_up_speed = 1.5
material_standby_temperature = 100
material_print_temperature = =default_material_print_temperature - 5 material_print_temperature = =default_material_print_temperature - 5
material_print_temperature_layer_0 = =material_print_temperature + 15 material_print_temperature_layer_0 = =material_print_temperature + 15
material_initial_print_temperature = =material_print_temperature - 5 material_initial_print_temperature = =material_print_temperature - 5

View File

@ -17,7 +17,6 @@ machine_nozzle_heat_up_speed = 1.5
material_print_temperature_layer_0 = =material_print_temperature + 10 material_print_temperature_layer_0 = =material_print_temperature + 10
material_initial_print_temperature = =material_print_temperature - 5 material_initial_print_temperature = =material_print_temperature - 5
material_final_print_temperature = =material_print_temperature - 10 material_final_print_temperature = =material_print_temperature - 10
material_standby_temperature = 100
prime_tower_enable = False prime_tower_enable = False
speed_print = 55 speed_print = 55
speed_layer_0 = =math.ceil(speed_print * 20 / 55) speed_layer_0 = =math.ceil(speed_print * 20 / 55)

View File

@ -24,7 +24,6 @@ material_final_print_temperature = =material_print_temperature - 10
material_initial_print_temperature = =material_print_temperature - 5 material_initial_print_temperature = =material_print_temperature - 5
material_print_temperature = =default_material_print_temperature + 10 material_print_temperature = =default_material_print_temperature + 10
material_print_temperature_layer_0 = =material_print_temperature material_print_temperature_layer_0 = =material_print_temperature
material_standby_temperature = 100
multiple_mesh_overlap = 0 multiple_mesh_overlap = 0
prime_tower_enable = True prime_tower_enable = True
prime_tower_wipe_enabled = True prime_tower_wipe_enabled = True

View File

@ -24,7 +24,6 @@ material_final_print_temperature = =material_print_temperature - 10
material_initial_print_temperature = =material_print_temperature - 5 material_initial_print_temperature = =material_print_temperature - 5
material_print_temperature = =default_material_print_temperature + 10 material_print_temperature = =default_material_print_temperature + 10
material_print_temperature_layer_0 = =material_print_temperature material_print_temperature_layer_0 = =material_print_temperature
material_standby_temperature = 100
multiple_mesh_overlap = 0 multiple_mesh_overlap = 0
prime_tower_enable = True prime_tower_enable = True
prime_tower_wipe_enabled = True prime_tower_wipe_enabled = True

View File

@ -26,7 +26,6 @@ material_final_print_temperature = =material_print_temperature - 10
material_initial_print_temperature = =material_print_temperature - 5 material_initial_print_temperature = =material_print_temperature - 5
material_print_temperature = =default_material_print_temperature + 2 material_print_temperature = =default_material_print_temperature + 2
material_print_temperature_layer_0 = =material_print_temperature material_print_temperature_layer_0 = =material_print_temperature
material_standby_temperature = 100
multiple_mesh_overlap = 0 multiple_mesh_overlap = 0
prime_tower_enable = True prime_tower_enable = True
prime_tower_wipe_enabled = True prime_tower_wipe_enabled = True

View File

@ -25,7 +25,6 @@ material_final_print_temperature = =material_print_temperature - 10
material_initial_print_temperature = =material_print_temperature - 5 material_initial_print_temperature = =material_print_temperature - 5
material_print_temperature = =default_material_print_temperature + 5 material_print_temperature = =default_material_print_temperature + 5
material_print_temperature_layer_0 = =material_print_temperature material_print_temperature_layer_0 = =material_print_temperature
material_standby_temperature = 100
multiple_mesh_overlap = 0 multiple_mesh_overlap = 0
prime_tower_enable = True prime_tower_enable = True
prime_tower_wipe_enabled = True prime_tower_wipe_enabled = True

View File

@ -15,7 +15,6 @@ variant = AA 0.4
material_print_temperature = =default_material_print_temperature + 10 material_print_temperature = =default_material_print_temperature + 10
material_initial_print_temperature = =material_print_temperature - 5 material_initial_print_temperature = =material_print_temperature - 5
material_final_print_temperature = =material_print_temperature - 10 material_final_print_temperature = =material_print_temperature - 10
material_standby_temperature = 100
skin_overlap = 20 skin_overlap = 20
speed_print = 60 speed_print = 60
speed_layer_0 = =math.ceil(speed_print * 20 / 60) speed_layer_0 = =math.ceil(speed_print * 20 / 60)

View File

@ -16,7 +16,6 @@ cool_min_speed = 7
material_print_temperature = =default_material_print_temperature + 5 material_print_temperature = =default_material_print_temperature + 5
material_initial_print_temperature = =material_print_temperature - 5 material_initial_print_temperature = =material_print_temperature - 5
material_final_print_temperature = =material_print_temperature - 10 material_final_print_temperature = =material_print_temperature - 10
material_standby_temperature = 100
speed_print = 60 speed_print = 60
speed_layer_0 = =math.ceil(speed_print * 20 / 60) speed_layer_0 = =math.ceil(speed_print * 20 / 60)
speed_topbottom = =math.ceil(speed_print * 30 / 60) speed_topbottom = =math.ceil(speed_print * 30 / 60)

View File

@ -18,7 +18,6 @@ machine_nozzle_heat_up_speed = 1.5
material_print_temperature = =default_material_print_temperature - 5 material_print_temperature = =default_material_print_temperature - 5
material_initial_print_temperature = =material_print_temperature - 5 material_initial_print_temperature = =material_print_temperature - 5
material_final_print_temperature = =material_print_temperature - 10 material_final_print_temperature = =material_print_temperature - 10
material_standby_temperature = 100
speed_print = 50 speed_print = 50
speed_layer_0 = =math.ceil(speed_print * 20 / 50) speed_layer_0 = =math.ceil(speed_print * 20 / 50)
speed_topbottom = =math.ceil(speed_print * 30 / 50) speed_topbottom = =math.ceil(speed_print * 30 / 50)

View File

@ -16,7 +16,6 @@ machine_nozzle_cool_down_speed = 0.85
machine_nozzle_heat_up_speed = 1.5 machine_nozzle_heat_up_speed = 1.5
material_initial_print_temperature = =material_print_temperature - 5 material_initial_print_temperature = =material_print_temperature - 5
material_final_print_temperature = =material_print_temperature - 10 material_final_print_temperature = =material_print_temperature - 10
material_standby_temperature = 100
speed_print = 55 speed_print = 55
speed_layer_0 = =math.ceil(speed_print * 20 / 55) speed_layer_0 = =math.ceil(speed_print * 20 / 55)
speed_topbottom = =math.ceil(speed_print * 30 / 55) speed_topbottom = =math.ceil(speed_print * 30 / 55)

View File

@ -18,7 +18,6 @@ cool_min_speed = 10
material_print_temperature = =default_material_print_temperature + 10 material_print_temperature = =default_material_print_temperature + 10
material_initial_print_temperature = =material_print_temperature - 5 material_initial_print_temperature = =material_print_temperature - 5
material_final_print_temperature = =material_print_temperature - 10 material_final_print_temperature = =material_print_temperature - 10
material_standby_temperature = 100
ooze_shield_angle = 40 ooze_shield_angle = 40
raft_acceleration = =acceleration_layer_0 raft_acceleration = =acceleration_layer_0
raft_airgap = =round(layer_height_0 * 0.85, 2) raft_airgap = =round(layer_height_0 * 0.85, 2)

View File

@ -18,7 +18,6 @@ cool_min_speed = 10
material_print_temperature = =default_material_print_temperature + 5 material_print_temperature = =default_material_print_temperature + 5
material_initial_print_temperature = =material_print_temperature - 5 material_initial_print_temperature = =material_print_temperature - 5
material_final_print_temperature = =material_print_temperature - 10 material_final_print_temperature = =material_print_temperature - 10
material_standby_temperature = 100
ooze_shield_angle = 40 ooze_shield_angle = 40
raft_acceleration = =acceleration_layer_0 raft_acceleration = =acceleration_layer_0
raft_airgap = =round(layer_height_0 * 0.85, 2) raft_airgap = =round(layer_height_0 * 0.85, 2)

View File

@ -17,7 +17,6 @@ cool_min_layer_time_fan_speed_max = 20
cool_min_speed = 15 cool_min_speed = 15
material_initial_print_temperature = =material_print_temperature - 5 material_initial_print_temperature = =material_print_temperature - 5
material_final_print_temperature = =material_print_temperature - 10 material_final_print_temperature = =material_print_temperature - 10
material_standby_temperature = 100
ooze_shield_angle = 40 ooze_shield_angle = 40
raft_acceleration = =acceleration_layer_0 raft_acceleration = =acceleration_layer_0
raft_airgap = =round(layer_height_0 * 0.85, 2) raft_airgap = =round(layer_height_0 * 0.85, 2)

View File

@ -17,7 +17,6 @@ cool_min_layer_time_fan_speed_max = 20
cool_min_speed = 12 cool_min_speed = 12
material_initial_print_temperature = =material_print_temperature - 5 material_initial_print_temperature = =material_print_temperature - 5
material_final_print_temperature = =material_print_temperature - 10 material_final_print_temperature = =material_print_temperature - 10
material_standby_temperature = 100
ooze_shield_angle = 40 ooze_shield_angle = 40
raft_acceleration = =acceleration_layer_0 raft_acceleration = =acceleration_layer_0
raft_airgap = =round(layer_height_0 * 0.85, 2) raft_airgap = =round(layer_height_0 * 0.85, 2)

View File

@ -31,7 +31,6 @@ machine_nozzle_heat_up_speed = 1.5
material_final_print_temperature = =material_print_temperature - 10 material_final_print_temperature = =material_print_temperature - 10
material_initial_print_temperature = =material_print_temperature - 5 material_initial_print_temperature = =material_print_temperature - 5
material_print_temperature = =default_material_print_temperature + 10 material_print_temperature = =default_material_print_temperature + 10
material_standby_temperature = 100
multiple_mesh_overlap = 0 multiple_mesh_overlap = 0
ooze_shield_angle = 40 ooze_shield_angle = 40
prime_tower_enable = True prime_tower_enable = True

View File

@ -29,7 +29,6 @@ machine_nozzle_heat_up_speed = 1.5
material_final_print_temperature = =material_print_temperature - 10 material_final_print_temperature = =material_print_temperature - 10
material_initial_print_temperature = =material_print_temperature - 5 material_initial_print_temperature = =material_print_temperature - 5
material_print_temperature = =default_material_print_temperature + 10 material_print_temperature = =default_material_print_temperature + 10
material_standby_temperature = 100
multiple_mesh_overlap = 0 multiple_mesh_overlap = 0
ooze_shield_angle = 40 ooze_shield_angle = 40
prime_tower_enable = True prime_tower_enable = True

View File

@ -31,7 +31,6 @@ machine_nozzle_heat_up_speed = 1.5
material_final_print_temperature = =material_print_temperature - 10 material_final_print_temperature = =material_print_temperature - 10
material_initial_print_temperature = =material_print_temperature - 5 material_initial_print_temperature = =material_print_temperature - 5
material_print_temperature = =default_material_print_temperature - 10 material_print_temperature = =default_material_print_temperature - 10
material_standby_temperature = 100
multiple_mesh_overlap = 0 multiple_mesh_overlap = 0
ooze_shield_angle = 40 ooze_shield_angle = 40
prime_tower_enable = True prime_tower_enable = True

View File

@ -28,7 +28,6 @@ machine_nozzle_cool_down_speed = 0.85
machine_nozzle_heat_up_speed = 1.5 machine_nozzle_heat_up_speed = 1.5
material_final_print_temperature = =material_print_temperature - 10 material_final_print_temperature = =material_print_temperature - 10
material_initial_print_temperature = =material_print_temperature - 5 material_initial_print_temperature = =material_print_temperature - 5
material_standby_temperature = 100
multiple_mesh_overlap = 0 multiple_mesh_overlap = 0
ooze_shield_angle = 40 ooze_shield_angle = 40
prime_tower_enable = True prime_tower_enable = True

View File

@ -15,7 +15,6 @@ variant = AA 0.4
material_print_temperature = =default_material_print_temperature + 5 material_print_temperature = =default_material_print_temperature + 5
material_initial_print_temperature = =material_print_temperature material_initial_print_temperature = =material_print_temperature
material_final_print_temperature = =material_print_temperature - 5 material_final_print_temperature = =material_print_temperature - 5
material_standby_temperature = 100
skin_overlap = 20 skin_overlap = 20
speed_print = 60 speed_print = 60
speed_layer_0 = =math.ceil(speed_print * 20 / 60) speed_layer_0 = =math.ceil(speed_print * 20 / 60)

View File

@ -16,7 +16,6 @@ cool_min_speed = 7
material_print_temperature = =default_material_print_temperature material_print_temperature = =default_material_print_temperature
material_initial_print_temperature = =material_print_temperature - 5 material_initial_print_temperature = =material_print_temperature - 5
material_final_print_temperature = =material_print_temperature - 10 material_final_print_temperature = =material_print_temperature - 10
material_standby_temperature = 100
speed_print = 60 speed_print = 60
speed_layer_0 = =math.ceil(speed_print * 20 / 60) speed_layer_0 = =math.ceil(speed_print * 20 / 60)
speed_topbottom = =math.ceil(speed_print * 30 / 60) speed_topbottom = =math.ceil(speed_print * 30 / 60)

View File

@ -17,7 +17,6 @@ machine_nozzle_heat_up_speed = 1.5
material_print_temperature = =default_material_print_temperature - 5 material_print_temperature = =default_material_print_temperature - 5
material_initial_print_temperature = =material_print_temperature - 10 material_initial_print_temperature = =material_print_temperature - 10
material_final_print_temperature = =material_print_temperature - 15 material_final_print_temperature = =material_print_temperature - 15
material_standby_temperature = 100
speed_print = 55 speed_print = 55
speed_layer_0 = =math.ceil(speed_print * 20 / 55) speed_layer_0 = =math.ceil(speed_print * 20 / 55)
speed_topbottom = =math.ceil(speed_print * 30 / 55) speed_topbottom = =math.ceil(speed_print * 30 / 55)

View File

@ -17,7 +17,6 @@ cool_fan_speed_max = =cool_fan_speed
machine_nozzle_cool_down_speed = 0.75 machine_nozzle_cool_down_speed = 0.75
machine_nozzle_heat_up_speed = 1.6 machine_nozzle_heat_up_speed = 1.6
material_print_temperature = =default_material_print_temperature + 5 material_print_temperature = =default_material_print_temperature + 5
material_standby_temperature = 100
prime_tower_enable = False prime_tower_enable = False
skin_overlap = 20 skin_overlap = 20
speed_layer_0 = =math.ceil(speed_print * 20 / 70) speed_layer_0 = =math.ceil(speed_print * 20 / 70)

View File

@ -16,7 +16,6 @@ cool_fan_full_at_height = =layer_height_0 + 2 * layer_height
cool_fan_speed_max = =cool_fan_speed cool_fan_speed_max = =cool_fan_speed
machine_nozzle_cool_down_speed = 0.75 machine_nozzle_cool_down_speed = 0.75
machine_nozzle_heat_up_speed = 1.6 machine_nozzle_heat_up_speed = 1.6
material_standby_temperature = 100
prime_tower_enable = False prime_tower_enable = False
speed_print = 80 speed_print = 80
speed_layer_0 = =math.ceil(speed_print * 20 / 80) speed_layer_0 = =math.ceil(speed_print * 20 / 80)

View File

@ -18,7 +18,6 @@ cool_min_speed = 10
machine_nozzle_cool_down_speed = 0.75 machine_nozzle_cool_down_speed = 0.75
machine_nozzle_heat_up_speed = 1.6 machine_nozzle_heat_up_speed = 1.6
material_print_temperature = =default_material_print_temperature - 5 material_print_temperature = =default_material_print_temperature - 5
material_standby_temperature = 100
prime_tower_enable = False prime_tower_enable = False
skin_overlap = 10 skin_overlap = 10
speed_print = 60 speed_print = 60

View File

@ -17,7 +17,6 @@ cool_fan_speed_max = =cool_fan_speed
cool_min_speed = 7 cool_min_speed = 7
machine_nozzle_cool_down_speed = 0.75 machine_nozzle_cool_down_speed = 0.75
machine_nozzle_heat_up_speed = 1.6 machine_nozzle_heat_up_speed = 1.6
material_standby_temperature = 100
prime_tower_enable = False prime_tower_enable = False
skin_overlap = 10 skin_overlap = 10
speed_layer_0 = =math.ceil(speed_print * 20 / 70) speed_layer_0 = =math.ceil(speed_print * 20 / 70)

View File

@ -32,7 +32,6 @@ material_final_print_temperature = =material_print_temperature - 10
material_initial_print_temperature = =material_print_temperature - 5 material_initial_print_temperature = =material_print_temperature - 5
material_print_temperature = =default_material_print_temperature - 5 material_print_temperature = =default_material_print_temperature - 5
material_print_temperature_layer_0 = =material_print_temperature + 5 material_print_temperature_layer_0 = =material_print_temperature + 5
material_standby_temperature = 100
multiple_mesh_overlap = 0 multiple_mesh_overlap = 0
prime_tower_enable = False prime_tower_enable = False
prime_tower_size = 16 prime_tower_size = 16

View File

@ -32,7 +32,6 @@ material_final_print_temperature = =material_print_temperature - 12
material_initial_print_temperature = =material_print_temperature - 2 material_initial_print_temperature = =material_print_temperature - 2
material_print_temperature = =default_material_print_temperature - 13 material_print_temperature = =default_material_print_temperature - 13
material_print_temperature_layer_0 = =material_print_temperature + 3 material_print_temperature_layer_0 = =material_print_temperature + 3
material_standby_temperature = 100
multiple_mesh_overlap = 0 multiple_mesh_overlap = 0
prime_tower_enable = False prime_tower_enable = False
prime_tower_size = 16 prime_tower_size = 16

View File

@ -30,7 +30,6 @@ material_final_print_temperature = =material_print_temperature - 10
material_initial_print_temperature = =material_print_temperature - 5 material_initial_print_temperature = =material_print_temperature - 5
material_print_temperature = =default_material_print_temperature - 15 material_print_temperature = =default_material_print_temperature - 15
material_print_temperature_layer_0 = =material_print_temperature + 3 material_print_temperature_layer_0 = =material_print_temperature + 3
material_standby_temperature = 100
multiple_mesh_overlap = 0 multiple_mesh_overlap = 0
prime_tower_enable = False prime_tower_enable = False
prime_tower_size = 16 prime_tower_size = 16

View File

@ -20,7 +20,6 @@ layer_height_0 = 0.2
machine_nozzle_cool_down_speed = 0.75 machine_nozzle_cool_down_speed = 0.75
machine_nozzle_heat_up_speed = 1.6 machine_nozzle_heat_up_speed = 1.6
material_print_temperature = =default_material_print_temperature -10 material_print_temperature = =default_material_print_temperature -10
material_standby_temperature = 100
prime_tower_enable = False prime_tower_enable = False
skin_overlap = 20 skin_overlap = 20
speed_layer_0 = =math.ceil(speed_print * 20 / 50) speed_layer_0 = =math.ceil(speed_print * 20 / 50)

View File

@ -18,7 +18,6 @@ layer_height_0 = 0.2
machine_nozzle_cool_down_speed = 0.75 machine_nozzle_cool_down_speed = 0.75
machine_nozzle_heat_up_speed = 1.6 machine_nozzle_heat_up_speed = 1.6
material_print_temperature = =default_material_print_temperature -10 material_print_temperature = =default_material_print_temperature -10
material_standby_temperature = 100
prime_tower_enable = False prime_tower_enable = False
speed_layer_0 = =math.ceil(speed_print * 20 / 45) speed_layer_0 = =math.ceil(speed_print * 20 / 45)
speed_print = 45 speed_print = 45

View File

@ -19,7 +19,6 @@ layer_height_0 = 0.2
machine_nozzle_cool_down_speed = 0.75 machine_nozzle_cool_down_speed = 0.75
machine_nozzle_heat_up_speed = 1.6 machine_nozzle_heat_up_speed = 1.6
material_print_temperature = =default_material_print_temperature - 15 material_print_temperature = =default_material_print_temperature - 15
material_standby_temperature = 100
prime_tower_enable = False prime_tower_enable = False
skin_overlap = 10 skin_overlap = 10
speed_layer_0 = =math.ceil(speed_print * 20 / 45) speed_layer_0 = =math.ceil(speed_print * 20 / 45)

View File

@ -32,7 +32,6 @@ material_flow = 106
material_initial_print_temperature = =material_print_temperature material_initial_print_temperature = =material_print_temperature
material_print_temperature = =default_material_print_temperature + 2 material_print_temperature = =default_material_print_temperature + 2
material_print_temperature_layer_0 = =material_print_temperature + 15 material_print_temperature_layer_0 = =material_print_temperature + 15
material_standby_temperature = 100
multiple_mesh_overlap = 0 multiple_mesh_overlap = 0
prime_tower_wipe_enabled = True prime_tower_wipe_enabled = True
retraction_count_max = 15 retraction_count_max = 15

View File

@ -32,7 +32,6 @@ material_flow = 106
material_initial_print_temperature = =material_print_temperature material_initial_print_temperature = =material_print_temperature
material_print_temperature = =default_material_print_temperature + 2 material_print_temperature = =default_material_print_temperature + 2
material_print_temperature_layer_0 = =material_print_temperature + 15 material_print_temperature_layer_0 = =material_print_temperature + 15
material_standby_temperature = 100
multiple_mesh_overlap = 0 multiple_mesh_overlap = 0
prime_tower_wipe_enabled = True prime_tower_wipe_enabled = True
retraction_amount = 7 retraction_amount = 7

View File

@ -30,7 +30,6 @@ material_final_print_temperature = =material_print_temperature
material_flow = 106 material_flow = 106
material_initial_print_temperature = =material_print_temperature material_initial_print_temperature = =material_print_temperature
material_print_temperature_layer_0 = =material_print_temperature + 17 material_print_temperature_layer_0 = =material_print_temperature + 17
material_standby_temperature = 100
multiple_mesh_overlap = 0 multiple_mesh_overlap = 0
prime_tower_wipe_enabled = True prime_tower_wipe_enabled = True
retraction_count_max = 15 retraction_count_max = 15

View File

@ -13,7 +13,6 @@ variant = AA 0.8
[values] [values]
material_print_temperature = =default_material_print_temperature + 25 material_print_temperature = =default_material_print_temperature + 25
material_standby_temperature = 100
speed_print = 50 speed_print = 50
speed_topbottom = =math.ceil(speed_print * 30 / 50) speed_topbottom = =math.ceil(speed_print * 30 / 50)
speed_wall = =math.ceil(speed_print * 40 / 50) speed_wall = =math.ceil(speed_print * 40 / 50)

View File

@ -14,7 +14,6 @@ variant = AA 0.8
[values] [values]
layer_height = 0.4 layer_height = 0.4
material_print_temperature = =default_material_print_temperature + 30 material_print_temperature = =default_material_print_temperature + 30
material_standby_temperature = 100
speed_print = 50 speed_print = 50
speed_topbottom = =math.ceil(speed_print * 30 / 50) speed_topbottom = =math.ceil(speed_print * 30 / 50)
speed_wall = =math.ceil(speed_print * 37 / 50) speed_wall = =math.ceil(speed_print * 37 / 50)

View File

@ -14,7 +14,6 @@ variant = AA 0.8
[values] [values]
layer_height = 0.3 layer_height = 0.3
material_print_temperature = =default_material_print_temperature + 27 material_print_temperature = =default_material_print_temperature + 27
material_standby_temperature = 100
speed_print = 50 speed_print = 50
speed_topbottom = =math.ceil(speed_print * 30 / 50) speed_topbottom = =math.ceil(speed_print * 30 / 50)
speed_wall = =math.ceil(speed_print * 40 / 50) speed_wall = =math.ceil(speed_print * 40 / 50)

View File

@ -19,7 +19,6 @@ machine_nozzle_cool_down_speed = 0.9
machine_nozzle_heat_up_speed = 1.4 machine_nozzle_heat_up_speed = 1.4
material_print_temperature = =default_material_print_temperature - 10 material_print_temperature = =default_material_print_temperature - 10
material_print_temperature_layer_0 = =material_print_temperature material_print_temperature_layer_0 = =material_print_temperature
material_standby_temperature = 100
prime_tower_enable = True prime_tower_enable = True
retraction_hop = 0.1 retraction_hop = 0.1
retraction_hop_enabled = False retraction_hop_enabled = False

View File

@ -20,7 +20,6 @@ machine_nozzle_cool_down_speed = 0.9
machine_nozzle_heat_up_speed = 1.4 machine_nozzle_heat_up_speed = 1.4
material_print_temperature = =default_material_print_temperature - 5 material_print_temperature = =default_material_print_temperature - 5
material_print_temperature_layer_0 = =material_print_temperature material_print_temperature_layer_0 = =material_print_temperature
material_standby_temperature = 100
prime_tower_enable = True prime_tower_enable = True
retraction_hop = 0.1 retraction_hop = 0.1
retraction_hop_enabled = False retraction_hop_enabled = False

View File

@ -21,7 +21,6 @@ machine_nozzle_cool_down_speed = 0.9
machine_nozzle_heat_up_speed = 1.4 machine_nozzle_heat_up_speed = 1.4
material_print_temperature = =default_material_print_temperature - 7 material_print_temperature = =default_material_print_temperature - 7
material_print_temperature_layer_0 = =material_print_temperature material_print_temperature_layer_0 = =material_print_temperature
material_standby_temperature = 100
prime_tower_enable = True prime_tower_enable = True
retraction_hop = 0.1 retraction_hop = 0.1
retraction_hop_enabled = False retraction_hop_enabled = False

View File

@ -14,7 +14,6 @@ variant = AA 0.8
[values] [values]
brim_width = 15 brim_width = 15
material_print_temperature = =default_material_print_temperature + 15 material_print_temperature = =default_material_print_temperature + 15
material_standby_temperature = 100
prime_tower_enable = True prime_tower_enable = True
speed_print = 40 speed_print = 40
speed_topbottom = =math.ceil(speed_print * 25 / 40) speed_topbottom = =math.ceil(speed_print * 25 / 40)

View File

@ -15,7 +15,6 @@ variant = AA 0.8
brim_width = 15 brim_width = 15
layer_height = 0.4 layer_height = 0.4
material_print_temperature = =default_material_print_temperature + 20 material_print_temperature = =default_material_print_temperature + 20
material_standby_temperature = 100
prime_tower_enable = True prime_tower_enable = True
speed_print = 45 speed_print = 45
speed_topbottom = =math.ceil(speed_print * 30 / 45) speed_topbottom = =math.ceil(speed_print * 30 / 45)

View File

@ -15,7 +15,6 @@ variant = AA 0.8
brim_width = 15 brim_width = 15
layer_height = 0.3 layer_height = 0.3
material_print_temperature = =default_material_print_temperature + 17 material_print_temperature = =default_material_print_temperature + 17
material_standby_temperature = 100
prime_tower_enable = True prime_tower_enable = True
speed_print = 40 speed_print = 40
speed_topbottom = =math.ceil(speed_print * 25 / 40) speed_topbottom = =math.ceil(speed_print * 25 / 40)

View File

@ -17,7 +17,6 @@ cool_min_layer_time_fan_speed_max = 20
cool_min_speed = 10 cool_min_speed = 10
machine_nozzle_cool_down_speed = 0.9 machine_nozzle_cool_down_speed = 0.9
machine_nozzle_heat_up_speed = 1.4 machine_nozzle_heat_up_speed = 1.4
material_standby_temperature = 100
ooze_shield_angle = 40 ooze_shield_angle = 40
prime_tower_enable = True prime_tower_enable = True
raft_acceleration = =acceleration_layer_0 raft_acceleration = =acceleration_layer_0

View File

@ -18,7 +18,6 @@ cool_min_speed = 10
layer_height = 0.4 layer_height = 0.4
machine_nozzle_cool_down_speed = 0.9 machine_nozzle_cool_down_speed = 0.9
machine_nozzle_heat_up_speed = 1.4 machine_nozzle_heat_up_speed = 1.4
material_standby_temperature = 100
ooze_shield_angle = 40 ooze_shield_angle = 40
prime_tower_enable = True prime_tower_enable = True
raft_acceleration = =acceleration_layer_0 raft_acceleration = =acceleration_layer_0

View File

@ -18,7 +18,6 @@ cool_min_speed = 10
layer_height = 0.3 layer_height = 0.3
machine_nozzle_cool_down_speed = 0.9 machine_nozzle_cool_down_speed = 0.9
machine_nozzle_heat_up_speed = 1.4 machine_nozzle_heat_up_speed = 1.4
material_standby_temperature = 100
ooze_shield_angle = 40 ooze_shield_angle = 40
prime_tower_enable = True prime_tower_enable = True
raft_acceleration = =acceleration_layer_0 raft_acceleration = =acceleration_layer_0

View File

@ -17,7 +17,6 @@ brim_width = 14
cool_fan_full_at_height = =layer_height_0 + 14 * layer_height cool_fan_full_at_height = =layer_height_0 + 14 * layer_height
material_print_temperature = =default_material_print_temperature - 5 material_print_temperature = =default_material_print_temperature - 5
material_print_temperature_layer_0 = =material_print_temperature material_print_temperature_layer_0 = =material_print_temperature
material_standby_temperature = 100
raft_airgap = 0.5 raft_airgap = 0.5
raft_margin = 15 raft_margin = 15
skin_overlap = 0 skin_overlap = 0

View File

@ -18,7 +18,6 @@ cool_fan_full_at_height = =layer_height_0 + 7 * layer_height
layer_height = 0.4 layer_height = 0.4
material_print_temperature_layer_0 = =material_print_temperature material_print_temperature_layer_0 = =material_print_temperature
material_standby_temperature = 100
raft_airgap = 0.5 raft_airgap = 0.5
raft_margin = 15 raft_margin = 15
skin_overlap = 0 skin_overlap = 0

View File

@ -19,7 +19,6 @@ cool_fan_full_at_height = =layer_height_0 + 9 * layer_height
layer_height = 0.3 layer_height = 0.3
material_print_temperature = =default_material_print_temperature - 2 material_print_temperature = =default_material_print_temperature - 2
material_print_temperature_layer_0 = =material_print_temperature material_print_temperature_layer_0 = =material_print_temperature
material_standby_temperature = 100
raft_airgap = 0.5 raft_airgap = 0.5
raft_margin = 15 raft_margin = 15
skin_overlap = 0 skin_overlap = 0

Some files were not shown because too many files have changed in this diff Show More