Merge branch 'Ultimaker:master' into master

This commit is contained in:
goofoo3d 2021-10-31 10:54:20 +08:00 committed by GitHub
commit 33a5b4a115
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4324 changed files with 25716 additions and 11888 deletions

View File

@ -64,7 +64,7 @@ body:
You can find your log file here:
Windows: `%APPDATA%\cura\<Cura version>\cura.log` or usually `C:\Users\\<your username>\AppData\Roaming\cura\<Cura version>\cura.log`
MacOS: `$USER/Library/Application Support/cura/<Cura version>/cura.log`
Ubuntu/Linus: `$USER/.local/share/cura/<Cura version>/cura.log`
Ubuntu/Linux: `$USER/.local/share/cura/<Cura version>/cura.log`
If the Cura user interface still starts, you can also reach this directory from the application menu in Help -> Show settings folder
- type: checkboxes

View File

@ -33,7 +33,7 @@ configure_file(${CMAKE_SOURCE_DIR}/com.ultimaker.cura.desktop.in ${CMAKE_BINARY_
configure_file(cura/CuraVersion.py.in CuraVersion.py @ONLY)
# FIXME: The new FindPython3 finds the system's Python3.6 reather than the Python3.5 that we built for Cura's environment.
# FIXME: The new FindPython3 finds the system's Python3.6 rather than the Python3.5 that we built for Cura's environment.
# So we're using the old method here, with FindPythonInterp for now.
find_package(PythonInterp 3 REQUIRED)

View File

@ -4,7 +4,7 @@
include(CTest)
include(CMakeParseArguments)
# FIXME: The new FindPython3 finds the system's Python3.6 reather than the Python3.5 that we built for Cura's environment.
# FIXME: The new FindPython3 finds the system's Python3.6 rather than the Python3.5 that we built for Cura's environment.
# So we're using the old method here, with FindPythonInterp for now.
find_package(PythonInterp 3 REQUIRED)

View File

@ -1,7 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from datetime import datetime
from typing import Optional, Dict, TYPE_CHECKING, Callable
from typing import Any, Optional, Dict, TYPE_CHECKING, Callable
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty, QTimer, Q_ENUMS
@ -46,6 +46,9 @@ class Account(QObject):
loginStateChanged = pyqtSignal(bool)
"""Signal emitted when user logged in or out"""
additionalRightsChanged = pyqtSignal("QVariantMap")
"""Signal emitted when a users additional rights change"""
accessTokenChanged = pyqtSignal()
syncRequested = pyqtSignal()
"""Sync services may connect to this signal to receive sync triggers.
@ -59,7 +62,7 @@ class Account(QObject):
updatePackagesEnabledChanged = pyqtSignal(bool)
CLIENT_SCOPES = "account.user.read drive.backup.read drive.backup.write packages.download " \
"packages.rating.read packages.rating.write connect.cluster.read connect.cluster.write " \
"packages.rating.read packages.rating.write connect.cluster.read connect.cluster.write connect.material.write " \
"library.project.read library.project.write cura.printjob.read cura.printjob.write " \
"cura.mesh.read cura.mesh.write"
@ -70,6 +73,7 @@ class Account(QObject):
self._error_message = None # type: Optional[Message]
self._logged_in = False
self._additional_rights: Dict[str, Any] = {}
self._sync_state = SyncState.IDLE
self._manual_sync_enabled = False
self._update_packages_enabled = False
@ -301,3 +305,14 @@ class Account(QObject):
return # Nothing to do, user isn't logged in.
self._authorization_service.deleteAuthData()
def updateAdditionalRight(self, **kwargs) -> None:
"""Update the additional rights of the account.
The argument(s) are the rights that need to be set"""
self._additional_rights.update(kwargs)
self.additionalRightsChanged.emit(self._additional_rights)
@pyqtProperty("QVariantMap", notify = additionalRightsChanged)
def additionalRights(self) -> Dict[str, Any]:
"""A dictionary which can be queried for additional account rights."""
return self._additional_rights

View File

@ -13,7 +13,7 @@ DEFAULT_CURA_DEBUG_MODE = False
# Each release has a fixed SDK version coupled with it. It doesn't make sense to make it configurable because, for
# example Cura 3.2 with SDK version 6.1 will not work. So the SDK version is hard-coded here and left out of the
# CuraVersion.py.in template.
CuraSDKVersion = "7.7.0"
CuraSDKVersion = "7.8.0"
try:
from cura.CuraVersion import CuraAppName # type: ignore

View File

@ -110,18 +110,11 @@ def findNodePlacement(nodes_to_arrange: List["SceneNode"], build_volume: "BuildV
return found_solution_for_all, node_items
def arrange(nodes_to_arrange: List["SceneNode"], build_volume: "BuildVolume", fixed_nodes: Optional[List["SceneNode"]] = None, factor = 10000, add_new_nodes_in_scene: bool = False) -> bool:
"""
Find placement for a set of scene nodes, and move them by using a single grouped operation.
:param nodes_to_arrange: The list of nodes that need to be moved.
:param build_volume: The build volume that we want to place the nodes in. It gets size & disallowed areas from this.
:param fixed_nodes: List of nods that should not be moved, but should be used when deciding where the others nodes
are placed.
:param factor: The library that we use is int based. This factor defines how accuracte we want it to be.
:param add_new_nodes_in_scene: Whether to create new scene nodes before applying the transformations and rotations
:return: found_solution_for_all: Whether the algorithm found a place on the buildplate for all the objects
"""
def createGroupOperationForArrange(nodes_to_arrange: List["SceneNode"],
build_volume: "BuildVolume",
fixed_nodes: Optional[List["SceneNode"]] = None,
factor = 10000,
add_new_nodes_in_scene: bool = False) -> Tuple[GroupedOperation, int]:
scene_root = Application.getInstance().getController().getScene().getRoot()
found_solution_for_all, node_items = findNodePlacement(nodes_to_arrange, build_volume, fixed_nodes, factor)
@ -143,6 +136,27 @@ def arrange(nodes_to_arrange: List["SceneNode"], build_volume: "BuildVolume", fi
grouped_operation.addOperation(
TranslateOperation(node, Vector(200, node.getWorldPosition().y, -not_fit_count * 20), set_position = True))
not_fit_count += 1
grouped_operation.push()
return found_solution_for_all
return grouped_operation, not_fit_count
def arrange(nodes_to_arrange: List["SceneNode"],
build_volume: "BuildVolume",
fixed_nodes: Optional[List["SceneNode"]] = None,
factor = 10000,
add_new_nodes_in_scene: bool = False) -> bool:
"""
Find placement for a set of scene nodes, and move them by using a single grouped operation.
:param nodes_to_arrange: The list of nodes that need to be moved.
:param build_volume: The build volume that we want to place the nodes in. It gets size & disallowed areas from this.
:param fixed_nodes: List of nods that should not be moved, but should be used when deciding where the others nodes
are placed.
:param factor: The library that we use is int based. This factor defines how accuracte we want it to be.
:param add_new_nodes_in_scene: Whether to create new scene nodes before applying the transformations and rotations
:return: found_solution_for_all: Whether the algorithm found a place on the buildplate for all the objects
"""
grouped_operation, not_fit_count = createGroupOperationForArrange(nodes_to_arrange, build_volume, fixed_nodes, factor, add_new_nodes_in_scene)
grouped_operation.push()
return not_fit_count == 0

View File

@ -1,4 +1,4 @@
# Copyright (c) 2018 Ultimaker B.V.
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import io
@ -168,7 +168,10 @@ class Backup:
preferences_file = Resources.getPath(Resources.Preferences, "{}.cfg".format(preferences_file_name))
backup_preferences_file = os.path.join(version_data_dir, "{}.cfg".format(preferences_file_name))
Logger.log("d", "Moving preferences file from %s to %s", backup_preferences_file, preferences_file)
try:
shutil.move(backup_preferences_file, preferences_file)
except EnvironmentError as e:
Logger.error(f"Unable to back-up preferences file: {type(e)} - {str(e)}")
# Read the preferences from the newly restored configuration (or else the cached Preferences will override the restored ones)
self._application.readPreferencesFromConfiguration()
@ -203,6 +206,8 @@ class Backup:
archive.extract(archive_filename, target_path)
except (PermissionError, EnvironmentError):
Logger.logException("e", f"Unable to extract the file {archive_filename} from the backup due to permission or file system errors.")
except UnicodeEncodeError:
Logger.error(f"Unable to extract the file {archive_filename} because of an encoding error.")
CuraApplication.getInstance().processEvents()
return True

View File

@ -1078,9 +1078,10 @@ class BuildVolume(SceneNode):
# setting does *not* have a limit_to_extruder setting (which means that we can't ask the global extruder what
# the value is.
adhesion_extruder = self._global_container_stack.getProperty("adhesion_extruder_nr", "value")
skirt_brim_line_width = self._global_container_stack.extruderList[int(adhesion_extruder)].getProperty("skirt_brim_line_width", "value")
adhesion_stack = self._global_container_stack.extruderList[int(adhesion_extruder)]
skirt_brim_line_width = adhesion_stack.getProperty("skirt_brim_line_width", "value")
initial_layer_line_width_factor = self._global_container_stack.getProperty("initial_layer_line_width_factor", "value")
initial_layer_line_width_factor = adhesion_stack.getProperty("initial_layer_line_width_factor", "value")
# Use brim width if brim is enabled OR the prime tower has a brim.
if adhesion_type == "brim":
brim_line_count = self._global_container_stack.getProperty("brim_line_count", "value")

View File

@ -129,7 +129,7 @@ class CuraApplication(QtApplication):
# SettingVersion represents the set of settings available in the machine/extruder definitions.
# You need to make sure that this version number needs to be increased if there is any non-backwards-compatible
# changes of the settings.
SettingVersion = 17
SettingVersion = 19
Created = False
@ -320,7 +320,7 @@ class CuraApplication(QtApplication):
super().initialize()
self._preferences.addPreference("cura/single_instance", False)
self._use_single_instance = self._preferences.getValue("cura/single_instance")
self._use_single_instance = self._preferences.getValue("cura/single_instance") or self._cli_args.single_instance
self.__sendCommandToSingleInstance()
self._initializeSettingDefinitions()
@ -750,7 +750,9 @@ class CuraApplication(QtApplication):
@pyqtSlot(str, result = QUrl)
def getDefaultPath(self, key):
default_path = self.getPreferences().getValue("local_file/%s" % key)
if os.path.exists(default_path):
return QUrl.fromLocalFile(default_path)
return QUrl()
@pyqtSlot(str, str)
def setDefaultPath(self, key, default_path):
@ -1312,9 +1314,9 @@ class CuraApplication(QtApplication):
if not isinstance(node, SceneNode):
continue
if not node.getMeshData() and not node.callDecoration("isGroup"):
continue # Node that doesnt have a mesh and is not a group.
continue # Node that doesn't have a mesh and is not a group.
if node.getParent() and node.getParent().callDecoration("isGroup") or node.getParent().callDecoration("isSliceable"):
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
continue # Grouped nodes don't need resetting as their parent (the group) is reset)
if not node.isSelectable():
continue # i.e. node with layer data
if not node.callDecoration("isSliceable") and not node.callDecoration("isGroup"):
@ -1332,9 +1334,9 @@ class CuraApplication(QtApplication):
if not isinstance(node, SceneNode):
continue
if not node.getMeshData() and not node.callDecoration("isGroup"):
continue # Node that doesnt have a mesh and is not a group.
continue # Node that doesn't have a mesh and is not a group.
if node.getParent() and node.getParent().callDecoration("isGroup"):
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
continue # Grouped nodes don't need resetting as their parent (the group) is reset)
if not node.isSelectable():
continue # i.e. node with layer data
nodes.append(node)
@ -1361,9 +1363,9 @@ class CuraApplication(QtApplication):
if not isinstance(node, SceneNode):
continue
if not node.getMeshData() and not node.callDecoration("isGroup"):
continue # Node that doesnt have a mesh and is not a group.
continue # Node that doesn't have a mesh and is not a group.
if node.getParent() and node.getParent().callDecoration("isGroup"):
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
continue # Grouped nodes don't need resetting as their parent (the group) is reset)
if not node.callDecoration("isSliceable") and not node.callDecoration("isGroup"):
continue # i.e. node with layer data
nodes.append(node)
@ -1390,7 +1392,7 @@ class CuraApplication(QtApplication):
continue
if not node.getMeshData() and not node.callDecoration("isGroup"):
continue # Node that doesnt have a mesh and is not a group.
continue # Node that doesn't have a mesh and is not a group.
parent_node = node.getParent()
if parent_node and parent_node.callDecoration("isGroup"):
@ -1418,11 +1420,11 @@ class CuraApplication(QtApplication):
continue
if not node.getMeshData() and not node.callDecoration("isGroup"):
continue # Node that doesnt have a mesh and is not a group.
continue # Node that doesn't have a mesh and is not a group.
parent_node = node.getParent()
if parent_node and parent_node.callDecoration("isGroup"):
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
continue # Grouped nodes don't need resetting as their parent (the group) is reset)
if not node.isSelectable():
continue # i.e. node with layer data
@ -2039,11 +2041,11 @@ class CuraApplication(QtApplication):
if not node.isEnabled():
continue
if (not node.getMeshData() and not node.callDecoration("getLayerData")) and not node.callDecoration("isGroup"):
continue # Node that doesnt have a mesh and is not a group.
continue # Node that doesn't have a mesh and is not a group.
if only_selectable and not node.isSelectable():
continue # Only remove nodes that are selectable.
if not node.callDecoration("isSliceable") and not node.callDecoration("getLayerData") and not node.callDecoration("isGroup"):
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
continue # Grouped nodes don't need resetting as their parent (the group) is reset)
nodes.append(node)
if nodes:
from UM.Operations.GroupedOperation import GroupedOperation

View File

@ -12,7 +12,7 @@ from cura.CuraApplication import CuraApplication
# Since Cura has a few pre-defined "space claims" for the locations of certain components, we've provided some structure
# to indicate this.
# MainComponent works in the same way the MainComponent of a stage.
# the stageMenuComponent returns an item that should be used somehwere in the stage menu. It's up to the active stage
# the stageMenuComponent returns an item that should be used somewhere in the stage menu. It's up to the active stage
# to actually do something with this.
class CuraView(View):
def __init__(self, parent = None, use_empty_menu_placeholder: bool = False) -> None:

View File

@ -146,7 +146,7 @@ class LayerPolygon:
# When the line type changes the index needs to be increased by 2.
indices[self._index_begin:self._index_end, :] += numpy.cumsum(needed_points_list[line_mesh_mask.ravel(), 0], dtype = numpy.int32).reshape((-1, 1))
# Each line segment goes from it's starting point p to p+1, offset by the vertex index.
# The -1 is to compensate for the neccecarily True value of needed_points_list[0,0] which causes an unwanted +1 in cumsum above.
# The -1 is to compensate for the necessarily True value of needed_points_list[0,0] which causes an unwanted +1 in cumsum above.
indices[self._index_begin:self._index_end, :] += numpy.array([self._vertex_begin - 1, self._vertex_begin])
self._build_cache_line_mesh_mask = None

View File

@ -97,8 +97,7 @@ class MachineErrorChecker(QObject):
def startErrorCheckPropertyChanged(self, key: str, property_name: str) -> None:
"""Start the error check for property changed
this is seperate from the startErrorCheck because it ignores a number property types
this is separate from the startErrorCheck because it ignores a number property types
:param key:
:param property_name:

View File

@ -59,6 +59,8 @@ class ExtrudersModel(ListModel):
defaultColors = ["#ffc924", "#86ec21", "#22eeee", "#245bff", "#9124ff", "#ff24c8"]
"""List of colours to display if there is no material or the material has no known colour. """
MaterialNameRole = Qt.UserRole + 13
def __init__(self, parent = None):
"""Initialises the extruders model, defining the roles and listening for changes in the data.
@ -79,6 +81,7 @@ class ExtrudersModel(ListModel):
self.addRoleName(self.MaterialBrandRole, "material_brand")
self.addRoleName(self.ColorNameRole, "color_name")
self.addRoleName(self.MaterialTypeRole, "material_type")
self.addRoleName(self.MaterialNameRole, "material_name")
self._update_extruder_timer = QTimer()
self._update_extruder_timer.setInterval(100)
self._update_extruder_timer.setSingleShot(True)
@ -199,8 +202,8 @@ class ExtrudersModel(ListModel):
"material_brand": material_brand,
"color_name": color_name,
"material_type": extruder.material.getMetaDataEntry("material") if extruder.material else "",
"material_name": extruder.material.getMetaDataEntry("name") if extruder.material else "",
}
items.append(item)
extruders_changed = True
@ -224,6 +227,7 @@ class ExtrudersModel(ListModel):
"material_brand": "",
"color_name": "",
"material_type": "",
"material_label": ""
}
items.append(item)
if self._items != items:

View File

@ -1,7 +1,8 @@
# Copyright (c) 2018 Ultimaker B.V.
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtCore import Qt, QTimer, pyqtProperty, pyqtSignal
from typing import Optional
from UM.Qt.ListModel import ListModel
from UM.i18n import i18nCatalog
@ -20,6 +21,7 @@ class GlobalStacksModel(ListModel):
MetaDataRole = Qt.UserRole + 5
DiscoverySourceRole = Qt.UserRole + 6 # For separating local and remote printers in the machine management page
RemovalWarningRole = Qt.UserRole + 7
IsOnlineRole = Qt.UserRole + 8
def __init__(self, parent = None) -> None:
super().__init__(parent)
@ -31,18 +33,49 @@ class GlobalStacksModel(ListModel):
self.addRoleName(self.HasRemoteConnectionRole, "hasRemoteConnection")
self.addRoleName(self.MetaDataRole, "metadata")
self.addRoleName(self.DiscoverySourceRole, "discoverySource")
self.addRoleName(self.IsOnlineRole, "isOnline")
self._change_timer = QTimer()
self._change_timer.setInterval(200)
self._change_timer.setSingleShot(True)
self._change_timer.timeout.connect(self._update)
self._filter_connection_type = None # type: Optional[ConnectionType]
self._filter_online_only = False
# Listen to changes
CuraContainerRegistry.getInstance().containerAdded.connect(self._onContainerChanged)
CuraContainerRegistry.getInstance().containerMetaDataChanged.connect(self._onContainerChanged)
CuraContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChanged)
self._updateDelayed()
filterConnectionTypeChanged = pyqtSignal()
def setFilterConnectionType(self, new_filter: Optional[ConnectionType]) -> None:
self._filter_connection_type = new_filter
@pyqtProperty(int, fset = setFilterConnectionType, notify = filterConnectionTypeChanged)
def filterConnectionType(self) -> int:
"""
The connection type to filter the list of printers by.
Only printers that match this connection type will be listed in the
model.
"""
if self._filter_connection_type is None:
return -1
return self._filter_connection_type.value
filterOnlineOnlyChanged = pyqtSignal()
def setFilterOnlineOnly(self, new_filter: bool) -> None:
self._filter_online_only = new_filter
@pyqtProperty(bool, fset = setFilterOnlineOnly, notify = filterOnlineOnlyChanged)
def filterOnlineOnly(self) -> bool:
"""
Whether to filter the global stacks to show only printers that are online.
"""
return self._filter_online_only
def _onContainerChanged(self, container) -> None:
"""Handler for container added/removed events from registry"""
@ -58,6 +91,10 @@ class GlobalStacksModel(ListModel):
container_stacks = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine")
for container_stack in container_stacks:
if self._filter_connection_type is not None: # We want to filter on connection types.
if not any((connection_type == self._filter_connection_type for connection_type in container_stack.configuredConnectionTypes)):
continue # No connection type on this printer matches the filter.
has_remote_connection = False
for connection_type in container_stack.configuredConnectionTypes:
@ -67,6 +104,10 @@ class GlobalStacksModel(ListModel):
if parseBool(container_stack.getMetaDataEntry("hidden", False)):
continue
is_online = container_stack.getMetaDataEntry("is_online", False)
if self._filter_online_only and not is_online:
continue
device_name = container_stack.getMetaDataEntry("group_name", container_stack.getName())
section_name = "Connected printers" if has_remote_connection else "Preset printers"
section_name = self._catalog.i18nc("@info:title", section_name)
@ -82,6 +123,7 @@ class GlobalStacksModel(ListModel):
"hasRemoteConnection": has_remote_connection,
"metadata": container_stack.getMetaData().copy(),
"discoverySource": section_name,
"removalWarning": removal_warning})
"removalWarning": removal_warning,
"isOnline": is_online})
items.sort(key=lambda i: (not i["hasRemoteConnection"], i["name"]))
self.setItems(items)

View File

@ -107,7 +107,7 @@ class IntentCategoryModel(ListModel):
qualities = IntentModel()
qualities.setIntentCategory(category)
result.append({
"name": IntentCategoryModel.translation(category, "name", catalog.i18nc("@label", "Unknown")),
"name": IntentCategoryModel.translation(category, "name", category),
"description": IntentCategoryModel.translation(category, "description", None),
"intent_category": category,
"weight": list(IntentCategoryModel._get_translations().keys()).index(category),

View File

@ -2,24 +2,28 @@
# Cura is released under the terms of the LGPLv3 or higher.
import copy # To duplicate materials.
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QUrl
from PyQt5.QtGui import QDesktopServices
from typing import Any, Dict, Optional, TYPE_CHECKING
import uuid # To generate new GUIDs for new materials.
import zipfile # To export all materials in a .zip archive.
from UM.Message import Message
from UM.i18n import i18nCatalog
from UM.Logger import Logger
from UM.Resources import Resources # To find QML files.
from UM.Signal import postponeSignals, CompressTechnique
import cura.CuraApplication # Imported like this to prevent circular imports.
import cura.CuraApplication # Imported like this to prevent cirmanagecular imports.
from cura.Machines.ContainerTree import ContainerTree
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry # To find the sets of materials belonging to each other, and currently loaded extruder stacks.
from cura.UltimakerCloud.CloudMaterialSync import CloudMaterialSync
if TYPE_CHECKING:
from cura.Machines.MaterialNode import MaterialNode
catalog = i18nCatalog("cura")
class MaterialManagementModel(QObject):
favoritesChanged = pyqtSignal(str)
"""Triggered when a favorite is added or removed.
@ -27,6 +31,66 @@ class MaterialManagementModel(QObject):
:param The base file of the material is provided as parameter when this emits
"""
def __init__(self, parent: Optional[QObject] = None) -> None:
super().__init__(parent = parent)
self._material_sync = CloudMaterialSync(parent=self)
self._checkIfNewMaterialsWereInstalled()
def _checkIfNewMaterialsWereInstalled(self) -> None:
"""
Checks whether new material packages were installed in the latest startup. If there were, then it shows
a message prompting the user to sync the materials with their printers.
"""
application = cura.CuraApplication.CuraApplication.getInstance()
for package_id, package_data in application.getPackageManager().getPackagesInstalledOnStartup().items():
if package_data["package_info"]["package_type"] == "material":
# At least one new material was installed
# TODO: This should be enabled again once CURA-8609 is merged
#self._showSyncNewMaterialsMessage()
break
def _showSyncNewMaterialsMessage(self) -> None:
sync_materials_message = Message(
text = catalog.i18nc("@action:button",
"Please sync the material profiles with your printers before starting to print."),
title = catalog.i18nc("@action:button", "New materials installed"),
message_type = Message.MessageType.WARNING,
lifetime = 0
)
sync_materials_message.addAction(
"sync",
name = catalog.i18nc("@action:button", "Sync materials with printers"),
icon = "",
description = "Sync your newly installed materials with your printers.",
button_align = Message.ActionButtonAlignment.ALIGN_RIGHT
)
sync_materials_message.addAction(
"learn_more",
name = catalog.i18nc("@action:button", "Learn more"),
icon = "",
description = "Learn more about syncing your newly installed materials with your printers.",
button_align = Message.ActionButtonAlignment.ALIGN_LEFT,
button_style = Message.ActionButtonStyle.LINK
)
sync_materials_message.actionTriggered.connect(self._onSyncMaterialsMessageActionTriggered)
# Show the message only if there are printers that support material export
container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry()
global_stacks = container_registry.findContainerStacks(type = "machine")
if any([stack.supportsMaterialExport for stack in global_stacks]):
sync_materials_message.show()
def _onSyncMaterialsMessageActionTriggered(self, sync_message: Message, sync_message_action: str):
if sync_message_action == "sync":
QDesktopServices.openUrl(QUrl("https://example.com/openSyncAllWindow"))
# self.openSyncAllWindow()
sync_message.hide()
elif sync_message_action == "learn_more":
QDesktopServices.openUrl(QUrl("https://support.ultimaker.com/hc/en-us/articles/360013137919?utm_source=cura&utm_medium=software&utm_campaign=sync-material-printer-message"))
@pyqtSlot("QVariant", result = bool)
def canMaterialBeRemoved(self, material_node: "MaterialNode") -> bool:
"""Can a certain material be deleted, or is it still in use in one of the container stacks anywhere?
@ -261,39 +325,10 @@ class MaterialManagementModel(QObject):
except ValueError: # Material was not in the favorites list.
Logger.log("w", "Material {material_base_file} was already not a favorite material.".format(material_base_file = material_base_file))
@pyqtSlot(result = QUrl)
def getPreferredExportAllPath(self) -> QUrl:
@pyqtSlot()
def openSyncAllWindow(self) -> None:
"""
Get the preferred path to export materials to.
Opens the window to sync all materials.
"""
self._material_sync.openSyncAllWindow()
If there is a removable drive, that should be the preferred path. Otherwise it should be the most recent local
file path.
:return: The preferred path to export all materials to.
"""
cura_application = cura.CuraApplication.CuraApplication.getInstance()
device_manager = cura_application.getOutputDeviceManager()
devices = device_manager.getOutputDevices()
for device in devices:
if device.__class__.__name__ == "RemovableDriveOutputDevice":
return QUrl.fromLocalFile(device.getId())
else: # No removable drives? Use local path.
return cura_application.getDefaultPath("dialog_material_path")
@pyqtSlot(QUrl)
def exportAll(self, file_path: QUrl) -> None:
"""
Export all materials to a certain file path.
:param file_path: The path to export the materials to.
"""
registry = CuraContainerRegistry.getInstance()
archive = zipfile.ZipFile(file_path.toLocalFile(), "w", compression = zipfile.ZIP_DEFLATED)
for metadata in registry.findInstanceContainersMetadata(type = "material"):
if metadata["base_file"] != metadata["id"]: # Only process base files.
continue
if metadata["id"] == "empty_material": # Don't export the empty material.
continue
material = registry.findContainers(id = metadata["id"])[0]
suffix = registry.getMimeTypeForContainer(type(material)).preferredSuffix
filename = metadata["id"] + "." + suffix
archive.writestr(filename, material.serialize())

View File

@ -41,10 +41,6 @@ class QualityProfilesDropDownMenuModel(ListModel):
machine_manager.activeQualityGroupChanged.connect(self._onChange)
machine_manager.activeMaterialChanged.connect(self._onChange)
machine_manager.activeVariantChanged.connect(self._onChange)
machine_manager.extruderChanged.connect(self._onChange)
extruder_manager = application.getExtruderManager()
extruder_manager.extrudersChanged.connect(self._onChange)
self._layer_height_unit = "" # This is cached

View File

@ -33,7 +33,7 @@ class SettingVisibilityPresetsModel(QObject):
if basic_item is not None:
basic_visibile_settings = ";".join(basic_item.settings)
else:
Logger.log("w", "Unable to find the basic visiblity preset.")
Logger.log("w", "Unable to find the basic visibility preset.")
basic_visibile_settings = ""
self._preferences = preferences

View File

@ -6,11 +6,15 @@ from typing import List
from UM.Application import Application
from UM.Job import Job
from UM.Math.Vector import Vector
from UM.Message import Message
from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
from UM.Operations.GroupedOperation import GroupedOperation
from UM.Operations.TranslateOperation import TranslateOperation
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Scene.SceneNode import SceneNode
from UM.i18n import i18nCatalog
from cura.Arranging.Nest2DArrange import arrange
from cura.Arranging.Nest2DArrange import arrange, createGroupOperationForArrange
i18n_catalog = i18nCatalog("cura")
@ -43,11 +47,11 @@ class MultiplyObjectsJob(Job):
# Only count sliceable objects
if node_.callDecoration("isSliceable"):
fixed_nodes.append(node_)
nodes_to_add_without_arrange = []
for node in self._objects:
# If object is part of a group, multiply group
current_node = node
while current_node.getParent() and (current_node.getParent().callDecoration("isGroup") or current_node.getParent().callDecoration("isSliceable")):
while current_node.getParent() and current_node.getParent().callDecoration("isGroup"):
current_node = current_node.getParent()
if current_node in processed_nodes:
@ -56,19 +60,38 @@ class MultiplyObjectsJob(Job):
for _ in range(self._count):
new_node = copy.deepcopy(node)
# Same build plate
build_plate_number = current_node.callDecoration("getBuildPlateNumber")
new_node.callDecoration("setBuildPlateNumber", build_plate_number)
for child in new_node.getChildren():
child.callDecoration("setBuildPlateNumber", build_plate_number)
if not current_node.getParent().callDecoration("isSliceable"):
nodes.append(new_node)
else:
# The node we're trying to place has another node that is sliceable as a parent.
# As such, we shouldn't arrange it (but it should be added to the scene!)
nodes_to_add_without_arrange.append(new_node)
new_node.setParent(current_node.getParent())
found_solution_for_all = True
group_operation = GroupedOperation()
if nodes:
found_solution_for_all = arrange(nodes, Application.getInstance().getBuildVolume(), fixed_nodes,
factor = 10000, add_new_nodes_in_scene = True)
group_operation, not_fit_count = createGroupOperationForArrange(nodes,
Application.getInstance().getBuildVolume(),
fixed_nodes,
factor = 10000,
add_new_nodes_in_scene = True)
found_solution_for_all = not_fit_count == 0
if nodes_to_add_without_arrange:
for nested_node in nodes_to_add_without_arrange:
group_operation.addOperation(AddSceneNodeOperation(nested_node, nested_node.getParent()))
# Move the node a tiny bit so it doesn't overlap with the existing one.
# This doesn't fix it if someone creates more than one duplicate, but it at least shows that something
# happened (and after moving it, it's clear that there are more underneath)
group_operation.addOperation(TranslateOperation(nested_node, Vector(2.5, 2.5, 2.5)))
group_operation.push()
status_message.hide()
if not found_solution_for_all:

View File

@ -99,7 +99,14 @@ class AuthorizationService:
# If no auth data exists, we should always log in again.
Logger.log("d", "There was no auth data or access token")
return None
try:
user_data = self._auth_helpers.parseJWT(self._auth_data.access_token)
except AttributeError:
# THis might seem a bit double, but we get crash reports about this (CURA-2N2 in sentry)
Logger.log("d", "There was no auth data or access token")
return None
if user_data:
# If the profile was found, we return it immediately.
return user_data
@ -120,7 +127,7 @@ class AuthorizationService:
return self._auth_helpers.parseJWT(self._auth_data.access_token)
def getAccessToken(self) -> Optional[str]:
"""Get the access token as provided by the repsonse data."""
"""Get the access token as provided by the response data."""
if self._auth_data is None:
Logger.log("d", "No auth data to retrieve the access_token from")

View File

@ -79,7 +79,7 @@ class PickingPass(RenderPass):
return -1
distance = output.pixel(px, py) # distance in micron, from in r, g & b channels
distance = (distance & 0x00ffffff) / 1000. # drop the alpha channel and covert to mm
distance = (distance & 0x00ffffff) / 1000. # drop the alpha channel and convert to mm
return distance
def getPickedPosition(self, x: int, y: int) -> Vector:

View File

@ -49,7 +49,7 @@ class FirmwareUpdater(QObject):
raise NotImplementedError("_updateFirmware needs to be implemented")
def _cleanupAfterUpdate(self) -> None:
"""Cleanup after a succesful update"""
"""Cleanup after a successful update"""
# Clean up for next attempt.
self._update_firmware_thread = Thread(target=self._updateFirmware, daemon=True, name = "FirmwareUpdateThread")

View File

@ -1,4 +1,4 @@
# Copyright (c) 2018 Ultimaker B.V.
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.FileHandler.FileHandler import FileHandler #For typing.
@ -114,6 +114,11 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
return b"".join(file_data_bytes_list)
def _update(self) -> None:
"""
Update the connection state of this device.
This is called on regular intervals.
"""
if self._last_response_time:
time_since_last_response = time() - self._last_response_time
else:
@ -127,11 +132,11 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
if time_since_last_response > self._timeout_time >= time_since_last_request:
# Go (or stay) into timeout.
if self._connection_state_before_timeout is None:
self._connection_state_before_timeout = self._connection_state
self._connection_state_before_timeout = self.connectionState
self.setConnectionState(ConnectionState.Closed)
elif self._connection_state == ConnectionState.Closed:
elif self.connectionState == ConnectionState.Closed:
# Go out of timeout.
if self._connection_state_before_timeout is not None: # sanity check, but it should never be None here
self.setConnectionState(self._connection_state_before_timeout)
@ -361,7 +366,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
self._last_response_time = time()
if self._connection_state == ConnectionState.Connecting:
if self.connectionState == ConnectionState.Connecting:
self.setConnectionState(ConnectionState.Connected)
callback_key = reply.url().toString() + str(reply.operation())
@ -414,6 +419,6 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
@pyqtProperty(str, constant = True)
def ipAddress(self) -> str:
"""IP adress of this printer"""
"""IP address of this printer"""
return self._address

View File

@ -1,11 +1,13 @@
# Copyright (c) 2018 Ultimaker B.V.
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from enum import IntEnum
from typing import Callable, List, Optional, Union
from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject, QTimer, QUrl
from PyQt5.QtWidgets import QMessageBox
import cura.CuraApplication # Imported like this to prevent circular imports.
from UM.Logger import Logger
from UM.Signal import signalemitter
from UM.Qt.QtApplication import QtApplication
@ -120,11 +122,22 @@ class PrinterOutputDevice(QObject, OutputDevice):
callback(QMessageBox.Yes)
def isConnected(self) -> bool:
return self._connection_state != ConnectionState.Closed and self._connection_state != ConnectionState.Error
"""
Returns whether we could theoretically send commands to this printer.
:return: `True` if we are connected, or `False` if not.
"""
return self.connectionState != ConnectionState.Closed and self.connectionState != ConnectionState.Error
def setConnectionState(self, connection_state: "ConnectionState") -> None:
if self._connection_state != connection_state:
"""
Store the connection state of the printer.
Causes everything that displays the connection state to update its QML models.
:param connection_state: The new connection state to store.
"""
if self.connectionState != connection_state:
self._connection_state = connection_state
cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack().setMetaDataEntry("is_online", self.isConnected())
self.connectionStateChanged.emit(self._id)
@pyqtProperty(int, constant = True)
@ -133,6 +146,10 @@ class PrinterOutputDevice(QObject, OutputDevice):
@pyqtProperty(int, notify = connectionStateChanged)
def connectionState(self) -> "ConnectionState":
"""
Get the connection state of the printer, e.g. whether it is connected, still connecting, error state, etc.
:return: The current connection state of this output device.
"""
return self._connection_state
def _update(self) -> None:

View File

@ -0,0 +1,256 @@
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import enum
import functools # For partial methods to use as callbacks with information pre-filled.
import json # To serialise metadata for API calls.
import os # To delete the archive when we're done.
from PyQt5.QtCore import QUrl
import tempfile # To create an archive before we upload it.
import cura.CuraApplication # Imported like this to prevent circular imports.
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry # To find all printers to upload to.
from cura.UltimakerCloud import UltimakerCloudConstants # To know where the API is.
from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope # To know how to communicate with this server.
from UM.i18n import i18nCatalog
from UM.Job import Job
from UM.Logger import Logger
from UM.Signal import Signal
from UM.TaskManagement.HttpRequestManager import HttpRequestManager # To call the API.
from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope
from typing import Any, cast, Dict, List, Optional, TYPE_CHECKING
if TYPE_CHECKING:
from PyQt5.QtNetwork import QNetworkReply
from cura.UltimakerCloud.CloudMaterialSync import CloudMaterialSync
catalog = i18nCatalog("cura")
class UploadMaterialsError(Exception):
"""
Class to indicate something went wrong while uploading.
"""
pass
class UploadMaterialsJob(Job):
"""
Job that uploads a set of materials to the Digital Factory.
The job has a number of stages:
- First, it generates an archive of all materials. This typically takes a lot of processing power during which the
GIL remains locked.
- Then it requests the API to upload an archive.
- Then it uploads the archive to the URL given by the first request.
- Then it tells the API that the archive can be distributed to the printers.
"""
UPLOAD_REQUEST_URL = f"{UltimakerCloudConstants.CuraCloudAPIRoot}/connect/v1/materials/upload"
UPLOAD_CONFIRM_URL = UltimakerCloudConstants.CuraCloudAPIRoot + "/connect/v1/clusters/{cluster_id}/printers/{cluster_printer_id}/action/import_material"
class Result(enum.IntEnum):
SUCCESS = 0
FAILED = 1
class PrinterStatus(enum.Enum):
UPLOADING = "uploading"
SUCCESS = "success"
FAILED = "failed"
def __init__(self, material_sync: "CloudMaterialSync"):
super().__init__()
self._material_sync = material_sync
self._scope = JsonDecoratorScope(UltimakerCloudScope(cura.CuraApplication.CuraApplication.getInstance())) # type: JsonDecoratorScope
self._archive_filename = None # type: Optional[str]
self._archive_remote_id = None # type: Optional[str] # ID that the server gives to this archive. Used to communicate about the archive to the server.
self._printer_sync_status = {} # type: Dict[str, str]
self._printer_metadata = [] # type: List[Dict[str, Any]]
self.processProgressChanged.connect(self._onProcessProgressChanged)
uploadCompleted = Signal() # Triggered when the job is really complete, including uploading to the cloud.
processProgressChanged = Signal() # Triggered when we've made progress creating the archive.
uploadProgressChanged = Signal() # Triggered when we've made progress with the complete job. This signal emits a progress fraction (0-1) as well as the status of every printer.
def run(self) -> None:
"""
Generates an archive of materials and starts uploading that archive to the cloud.
"""
self._printer_metadata = CuraContainerRegistry.getInstance().findContainerStacksMetadata(
type = "machine",
connection_type = "3", # Only cloud printers.
is_online = "True", # Only online printers. Otherwise the server gives an error.
host_guid = "*", # Required metadata field. Otherwise we get a KeyError.
um_cloud_cluster_id = "*" # Required metadata field. Otherwise we get a KeyError.
)
for printer in self._printer_metadata:
self._printer_sync_status[printer["host_guid"]] = self.PrinterStatus.UPLOADING.value
try:
archive_file = tempfile.NamedTemporaryFile("wb", delete = False)
archive_file.close()
self._archive_filename = archive_file.name
self._material_sync.exportAll(QUrl.fromLocalFile(self._archive_filename), notify_progress = self.processProgressChanged)
except OSError as e:
Logger.error(f"Failed to create archive of materials to sync with printers: {type(e)} - {e}")
self.failed(UploadMaterialsError(catalog.i18nc("@text:error", "Failed to create archive of materials to sync with printers.")))
return
try:
file_size = os.path.getsize(self._archive_filename)
except OSError as e:
Logger.error(f"Failed to load the archive of materials to sync it with printers: {type(e)} - {e}")
self.failed(UploadMaterialsError(catalog.i18nc("@text:error", "Failed to load the archive of materials to sync it with printers.")))
return
request_metadata = {
"data": {
"file_size": file_size,
"material_profile_name": "cura.umm", # File name can be anything as long as it's .umm. It's not used by anyone.
"content_type": "application/zip", # This endpoint won't receive files of different MIME types.
"origin": "cura" # Some identifier against hackers intercepting this upload request, apparently.
}
}
request_payload = json.dumps(request_metadata).encode("UTF-8")
http = HttpRequestManager.getInstance()
http.put(
url = self.UPLOAD_REQUEST_URL,
data = request_payload,
callback = self.onUploadRequestCompleted,
error_callback = self.onError,
scope = self._scope
)
def onUploadRequestCompleted(self, reply: "QNetworkReply") -> None:
"""
Triggered when we successfully requested to upload a material archive.
We then need to start uploading the material archive to the URL that the request answered with.
:param reply: The reply from the server to our request to upload an archive.
"""
response_data = HttpRequestManager.readJSON(reply)
if response_data is None:
Logger.error(f"Invalid response to material upload request. Could not parse JSON data.")
self.failed(UploadMaterialsError(catalog.i18nc("@text:error", "The response from Digital Factory appears to be corrupted.")))
return
if "data" not in response_data:
Logger.error(f"Invalid response to material upload request: Missing 'data' field that contains the entire response.")
self.failed(UploadMaterialsError(catalog.i18nc("@text:error", "The response from Digital Factory is missing important information.")))
return
if "upload_url" not in response_data["data"]:
Logger.error(f"Invalid response to material upload request: Missing 'upload_url' field to upload archive to.")
self.failed(UploadMaterialsError(catalog.i18nc("@text:error", "The response from Digital Factory is missing important information.")))
return
if "material_profile_id" not in response_data["data"]:
Logger.error(f"Invalid response to material upload request: Missing 'material_profile_id' to communicate about the materials with the server.")
self.failed(UploadMaterialsError(catalog.i18nc("@text:error", "The response from Digital Factory is missing important information.")))
return
upload_url = response_data["data"]["upload_url"]
self._archive_remote_id = response_data["data"]["material_profile_id"]
try:
with open(cast(str, self._archive_filename), "rb") as f:
file_data = f.read()
except OSError as e:
Logger.error(f"Failed to load archive back in for sending to cloud: {type(e)} - {e}")
self.failed(UploadMaterialsError(catalog.i18nc("@text:error", "Failed to load the archive of materials to sync it with printers.")))
return
http = HttpRequestManager.getInstance()
http.put(
url = upload_url,
data = file_data,
callback = self.onUploadCompleted,
error_callback = self.onError,
scope = self._scope
)
def onUploadCompleted(self, reply: "QNetworkReply") -> None:
"""
When we've successfully uploaded the archive to the cloud, we need to notify the API to start syncing that
archive to every printer.
:param reply: The reply from the cloud storage when the upload succeeded.
"""
for container_stack in self._printer_metadata:
cluster_id = container_stack["um_cloud_cluster_id"]
printer_id = container_stack["host_guid"]
http = HttpRequestManager.getInstance()
http.post(
url = self.UPLOAD_CONFIRM_URL.format(cluster_id = cluster_id, cluster_printer_id = printer_id),
callback = functools.partial(self.onUploadConfirmed, printer_id),
error_callback = functools.partial(self.onUploadConfirmed, printer_id), # Let this same function handle the error too.
scope = self._scope,
data = json.dumps({"data": {"material_profile_id": self._archive_remote_id}}).encode("UTF-8")
)
def onUploadConfirmed(self, printer_id: str, reply: "QNetworkReply", error: Optional["QNetworkReply.NetworkError"] = None) -> None:
"""
Triggered when we've got a confirmation that the material is synced with the printer, or that syncing failed.
If syncing succeeded we mark this printer as having the status "success". If it failed we mark the printer as
"failed". If this is the last upload that needed to be completed, we complete the job with either a success
state (every printer successfully synced) or a failed state (any printer failed).
:param printer_id: The printer host_guid that we completed syncing with.
:param reply: The reply that the server gave to confirm.
:param error: If the request failed, this error gives an indication what happened.
"""
if error is not None:
Logger.error(f"Failed to confirm uploading material archive to printer {printer_id}: {error}")
self._printer_sync_status[printer_id] = self.PrinterStatus.FAILED.value
else:
self._printer_sync_status[printer_id] = self.PrinterStatus.SUCCESS.value
still_uploading = len([val for val in self._printer_sync_status.values() if val == self.PrinterStatus.UPLOADING.value])
self.uploadProgressChanged.emit(0.8 + (len(self._printer_sync_status) - still_uploading) / len(self._printer_sync_status), self.getPrinterSyncStatus())
if still_uploading == 0: # This is the last response to be processed.
if self.PrinterStatus.FAILED.value in self._printer_sync_status.values():
self.setResult(self.Result.FAILED)
self.setError(UploadMaterialsError(catalog.i18nc("@text:error", "Failed to connect to Digital Factory to sync materials with some of the printers.")))
else:
self.setResult(self.Result.SUCCESS)
self.uploadCompleted.emit(self.getResult(), self.getError())
def onError(self, reply: "QNetworkReply", error: Optional["QNetworkReply.NetworkError"]) -> None:
"""
Used as callback from HTTP requests when the request failed.
The given network error from the `HttpRequestManager` is logged, and the job is marked as failed.
:param reply: The main reply of the server. This reply will most likely not be valid.
:param error: The network error (Qt's enum) that occurred.
"""
Logger.error(f"Failed to upload material archive: {error}")
self.failed(UploadMaterialsError(catalog.i18nc("@text:error", "Failed to connect to Digital Factory.")))
def getPrinterSyncStatus(self) -> Dict[str, str]:
"""
For each printer, identified by host_guid, this gives the current status of uploading the material archive.
The possible states are given in the PrinterStatus enum.
:return: A dictionary with printer host_guids as keys, and their status as values.
"""
return self._printer_sync_status
def failed(self, error: UploadMaterialsError) -> None:
"""
Helper function for when we have a general failure.
This sets the sync status for all printers to failed, sets the error on
the job and the result of the job to FAILED.
:param error: An error to show to the user.
"""
self.setResult(self.Result.FAILED)
self.setError(error)
for printer_id in self._printer_sync_status:
self._printer_sync_status[printer_id] = self.PrinterStatus.FAILED.value
self.uploadProgressChanged.emit(1.0, self.getPrinterSyncStatus())
self.uploadCompleted.emit(self.getResult(), self.getError())
def _onProcessProgressChanged(self, progress: float) -> None:
"""
When we progress in the process of uploading materials, we not only signal the new progress (float from 0 to 1)
but we also signal the current status of every printer. These are emitted as the two parameters of the signal.
:param progress: The progress of this job, between 0 and 1.
"""
self.uploadProgressChanged.emit(progress * 0.8, self.getPrinterSyncStatus()) # The processing is 80% of the progress bar.

View File

@ -23,6 +23,8 @@ from UM.Settings.InstanceContainer import InstanceContainer
import cura.CuraApplication
from cura.Machines.ContainerTree import ContainerTree
from cura.Settings.ExtruderStack import ExtruderStack
from cura.Settings.GlobalStack import GlobalStack
if TYPE_CHECKING:
from cura.CuraApplication import CuraApplication
@ -319,7 +321,7 @@ class ContainerManager(QObject):
stack.qualityChanges = quality_changes
if not quality_changes or container_registry.isReadOnly(quality_changes.getId()):
Logger.log("e", "Could not update quality of a nonexistant or read only quality profile in stack %s", stack.getId())
Logger.log("e", "Could not update quality of a nonexistent or read only quality profile in stack %s", stack.getId())
continue
self._performMerge(quality_changes, stack.getTop())
@ -408,7 +410,7 @@ class ContainerManager(QObject):
container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry()
for plugin_id, container_type in container_registry.getContainerTypes():
# Ignore default container types since those are not plugins
if container_type in (InstanceContainer, ContainerStack, DefinitionContainer):
if container_type in (InstanceContainer, ContainerStack, DefinitionContainer, GlobalStack, ExtruderStack):
continue
serialize_type = ""

View File

@ -32,6 +32,10 @@ from cura.Machines.ContainerTree import ContainerTree
from cura.ReaderWriters.ProfileReader import NoProfileException, ProfileReader
from UM.i18n import i18nCatalog
from .DatabaseHandlers.IntentDatabaseHandler import IntentDatabaseHandler
from .DatabaseHandlers.QualityDatabaseHandler import QualityDatabaseHandler
from .DatabaseHandlers.VariantDatabaseHandler import VariantDatabaseHandler
catalog = i18nCatalog("cura")
@ -44,6 +48,10 @@ class CuraContainerRegistry(ContainerRegistry):
# is added, we check to see if an extruder stack needs to be added.
self.containerAdded.connect(self._onContainerAdded)
self._database_handlers["variant"] = VariantDatabaseHandler()
self._database_handlers["quality"] = QualityDatabaseHandler()
self._database_handlers["intent"] = IntentDatabaseHandler()
@override(ContainerRegistry)
def addContainer(self, container: ContainerInterface) -> bool:
"""Overridden from ContainerRegistry

View File

@ -66,7 +66,7 @@ class CuraStackBuilder:
Logger.logException("e", "Failed to create an extruder stack for position {pos}: {err}".format(pos = position, err = str(e)))
return None
# If given, set the machine_extruder_count when creating the machine, or else the extruderList used bellow will
# If given, set the machine_extruder_count when creating the machine, or else the extruderList used below will
# not return the correct extruder list (since by default, the machine_extruder_count is 1) in machines with
# settable number of extruders.
if machine_extruder_count and 0 <= machine_extruder_count <= len(extruder_dict):

View File

@ -0,0 +1,25 @@
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Settings.SQLQueryFactory import SQLQueryFactory
from UM.Settings.DatabaseContainerMetadataController import DatabaseMetadataContainerController
from UM.Settings.InstanceContainer import InstanceContainer
class IntentDatabaseHandler(DatabaseMetadataContainerController):
"""The Database handler for Intent containers"""
def __init__(self) -> None:
super().__init__(SQLQueryFactory(table = "intent",
fields = {
"id": "text",
"name": "text",
"quality_type": "text",
"intent_category": "text",
"variant": "text",
"definition": "text",
"material": "text",
"version": "text",
"setting_version": "text"
}))
self._container_type = InstanceContainer

View File

@ -0,0 +1,38 @@
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Settings.SQLQueryFactory import SQLQueryFactory, metadata_type
from UM.Settings.DatabaseContainerMetadataController import DatabaseMetadataContainerController
from UM.Settings.InstanceContainer import InstanceContainer
class QualityDatabaseHandler(DatabaseMetadataContainerController):
"""The Database handler for Quality containers"""
def __init__(self):
super().__init__(SQLQueryFactory(table = "quality",
fields = {
"id": "text",
"name": "text",
"quality_type": "text",
"material": "text",
"variant": "text",
"global_quality": "bool",
"definition": "text",
"version": "text",
"setting_version": "text"
}))
self._container_type = InstanceContainer
def groomMetadata(self, metadata: metadata_type) -> metadata_type:
"""
Ensures that the metadata is in the order of the field keys and has the right size.
if the metadata doesn't contains a key which is stored in the DB it will add it as
an empty string. Key, value pairs that are not stored in the DB are dropped.
If the `global_quality` isn't set it well default to 'False'
:param metadata: The container metadata
"""
if "global_quality" not in metadata:
metadata["global_quality"] = "False"
return super().groomMetadata(metadata)

View File

@ -0,0 +1,22 @@
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Settings.SQLQueryFactory import SQLQueryFactory
from UM.Settings.DatabaseContainerMetadataController import DatabaseMetadataContainerController
from UM.Settings.InstanceContainer import InstanceContainer
class VariantDatabaseHandler(DatabaseMetadataContainerController):
"""The Database handler for Variant containers"""
def __init__(self):
super().__init__(SQLQueryFactory(table = "variant",
fields = {
"id": "text",
"name": "text",
"hardware_type": "text",
"definition": "text",
"version": "text",
"setting_version": "text"
}))
self._container_type = InstanceContainer

View File

@ -1,4 +1,4 @@
# Copyright (c) 2020 Ultimaker B.V.
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import time
@ -627,7 +627,7 @@ class MachineManager(QObject):
return ""
return global_container_stack.getIntentCategory()
# Provies a list of extruder positions that have a different intent from the active one.
# Provides a list of extruder positions that have a different intent from the active one.
@pyqtProperty("QStringList", notify=activeIntentChanged)
def extruderPositionsWithNonActiveIntent(self):
global_container_stack = self._application.getGlobalContainerStack()
@ -1398,6 +1398,8 @@ class MachineManager(QObject):
# previous one).
self._global_container_stack.setUserChanges(global_user_changes)
for i, user_changes in enumerate(per_extruder_user_changes):
if i >= len(self._global_container_stack.extruderList): # New printer has fewer extruders.
break
self._global_container_stack.extruderList[i].setUserChanges(per_extruder_user_changes[i])
@pyqtSlot(QObject)

View File

@ -18,6 +18,8 @@ class SingleInstance:
self._single_instance_server = None
self._application.getPreferences().addPreference("cura/single_instance_clear_before_load", True)
# Starts a client that checks for a single instance server and sends the files that need to opened if the server
# exists. Returns True if the single instance server is found, otherwise False.
def startClient(self) -> bool:
@ -42,6 +44,7 @@ class SingleInstance:
# "command" field is required and holds the name of the command to execute.
# Other fields depend on the command.
if self._application.getPreferences().getValue("cura/single_instance_clear_before_load"):
payload = {"command": "clear-all"}
single_instance_socket.write(bytes(json.dumps(payload) + "\n", encoding = "ascii"))
@ -68,7 +71,7 @@ class SingleInstance:
Logger.log("e", "Single instance server was not created.")
def _onClientConnected(self) -> None:
Logger.log("i", "New connection recevied on our single-instance server")
Logger.log("i", "New connection received on our single-instance server")
connection = None #type: Optional[QLocalSocket]
if self._single_instance_server:
connection = self._single_instance_server.nextPendingConnection()

View File

@ -56,8 +56,8 @@ class OnExitCallbackManager:
self._application.callLater(self._application.closeApplication)
# This is the callback function which an on-exit callback should call when it finishes, it should provide the
# "should_proceed" flag indicating whether this check has "passed", or in other words, whether quiting the
# application should be blocked. If the last on-exit callback doesn't block the quiting, it will call the next
# "should_proceed" flag indicating whether this check has "passed", or in other words, whether quitting the
# application should be blocked. If the last on-exit callback doesn't block the quitting, it will call the next
# registered on-exit callback if available.
def onCurrentCallbackFinished(self, should_proceed: bool = True) -> None:
if not should_proceed:

View File

@ -90,7 +90,7 @@ class ObjectsModel(ListModel):
parent = node.getParent()
if parent and parent.callDecoration("isGroup"):
return False # Grouped nodes don't need resetting as their parent (the group) is resetted)
return False # Grouped nodes don't need resetting as their parent (the group) is reset)
node_build_plate_number = node.callDecoration("getBuildPlateNumber")
if Application.getInstance().getPreferences().getValue("view/filter_current_build_plate") and node_build_plate_number != self._build_plate_number:

View File

@ -449,7 +449,4 @@ class PrintInformation(QObject):
if isinstance(output_device, ProjectOutputDevice):
new_name = output_device.getLastOutputName()
if new_name is not None:
if len(os.path.dirname(new_name)) > 0:
self.setProjectName(new_name)
else:
self.setJobName(new_name)
self.setJobName(os.path.splitext(os.path.basename(new_name))[0])

View File

@ -0,0 +1,217 @@
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl
from PyQt5.QtGui import QDesktopServices
from typing import Dict, Optional, TYPE_CHECKING
import zipfile # To export all materials in a .zip archive.
import cura.CuraApplication # Imported like this to prevent circular imports.
from UM.Resources import Resources
from cura.PrinterOutput.UploadMaterialsJob import UploadMaterialsJob, UploadMaterialsError # To export materials to the output printer.
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
from UM.i18n import i18nCatalog
from UM.Logger import Logger
from UM.Message import Message
if TYPE_CHECKING:
from UM.Signal import Signal
catalog = i18nCatalog("cura")
class CloudMaterialSync(QObject):
"""
Handles the synchronisation of material profiles with cloud accounts.
"""
def __init__(self, parent: QObject = None):
super().__init__(parent)
self.sync_all_dialog = None # type: Optional[QObject]
self._export_upload_status = "idle"
self._checkIfNewMaterialsWereInstalled()
self._export_progress = 0.0
self._printer_status = {} # type: Dict[str, str]
def _checkIfNewMaterialsWereInstalled(self) -> None:
"""
Checks whether new material packages were installed in the latest startup. If there were, then it shows
a message prompting the user to sync the materials with their printers.
"""
application = cura.CuraApplication.CuraApplication.getInstance()
for package_id, package_data in application.getPackageManager().getPackagesInstalledOnStartup().items():
if package_data["package_info"]["package_type"] == "material":
# At least one new material was installed
self._showSyncNewMaterialsMessage()
break
def openSyncAllWindow(self):
self.reset()
if self.sync_all_dialog is None:
qml_path = Resources.getPath(cura.CuraApplication.CuraApplication.ResourceTypes.QmlFiles, "Preferences",
"Materials", "MaterialsSyncDialog.qml")
self.sync_all_dialog = cura.CuraApplication.CuraApplication.getInstance().createQmlComponent(
qml_path, {})
if self.sync_all_dialog is None: # Failed to load QML file.
return
self.sync_all_dialog.setProperty("syncModel", self)
self.sync_all_dialog.setProperty("pageIndex", 0) # Return to first page.
self.sync_all_dialog.setProperty("hasExportedUsb", False) # If the user exported USB before, reset that page.
self.sync_all_dialog.show()
def _showSyncNewMaterialsMessage(self) -> None:
sync_materials_message = Message(
text = catalog.i18nc("@action:button",
"Please sync the material profiles with your printers before starting to print."),
title = catalog.i18nc("@action:button", "New materials installed"),
message_type = Message.MessageType.WARNING,
lifetime = 0
)
sync_materials_message.addAction(
"sync",
name = catalog.i18nc("@action:button", "Sync materials with printers"),
icon = "",
description = "Sync your newly installed materials with your printers.",
button_align = Message.ActionButtonAlignment.ALIGN_RIGHT
)
sync_materials_message.addAction(
"learn_more",
name = catalog.i18nc("@action:button", "Learn more"),
icon = "",
description = "Learn more about syncing your newly installed materials with your printers.",
button_align = Message.ActionButtonAlignment.ALIGN_LEFT,
button_style = Message.ActionButtonStyle.LINK
)
sync_materials_message.actionTriggered.connect(self._onSyncMaterialsMessageActionTriggered)
# Show the message only if there are printers that support material export
container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry()
global_stacks = container_registry.findContainerStacks(type = "machine")
if any([stack.supportsMaterialExport for stack in global_stacks]):
sync_materials_message.show()
def _onSyncMaterialsMessageActionTriggered(self, sync_message: Message, sync_message_action: str):
if sync_message_action == "sync":
self.openSyncAllWindow()
sync_message.hide()
elif sync_message_action == "learn_more":
QDesktopServices.openUrl(QUrl("https://support.ultimaker.com/hc/en-us/articles/360013137919?utm_source=cura&utm_medium=software&utm_campaign=sync-material-printer-message"))
@pyqtSlot(result = QUrl)
def getPreferredExportAllPath(self) -> QUrl:
"""
Get the preferred path to export materials to.
If there is a removable drive, that should be the preferred path. Otherwise it should be the most recent local
file path.
:return: The preferred path to export all materials to.
"""
cura_application = cura.CuraApplication.CuraApplication.getInstance()
device_manager = cura_application.getOutputDeviceManager()
devices = device_manager.getOutputDevices()
for device in devices:
if device.__class__.__name__ == "RemovableDriveOutputDevice":
return QUrl.fromLocalFile(device.getId())
else: # No removable drives? Use local path.
return cura_application.getDefaultPath("dialog_material_path")
@pyqtSlot(QUrl)
def exportAll(self, file_path: QUrl, notify_progress: Optional["Signal"] = None) -> None:
"""
Export all materials to a certain file path.
:param file_path: The path to export the materials to.
"""
registry = CuraContainerRegistry.getInstance()
# Create empty archive.
try:
archive = zipfile.ZipFile(file_path.toLocalFile(), "w", compression = zipfile.ZIP_DEFLATED)
except OSError as e:
Logger.log("e", f"Can't write to destination {file_path.toLocalFile()}: {type(e)} - {str(e)}")
error_message = Message(
text = catalog.i18nc("@message:text", "Could not save material archive to {}:").format(file_path.toLocalFile()) + " " + str(e),
title = catalog.i18nc("@message:title", "Failed to save material archive"),
message_type = Message.MessageType.ERROR
)
error_message.show()
return
materials_metadata = registry.findInstanceContainersMetadata(type = "material")
for index, metadata in enumerate(materials_metadata):
if notify_progress is not None:
progress = index / len(materials_metadata)
notify_progress.emit(progress)
if metadata["base_file"] != metadata["id"]: # Only process base files.
continue
if metadata["id"] == "empty_material": # Don't export the empty material.
continue
material = registry.findContainers(id = metadata["id"])[0]
suffix = registry.getMimeTypeForContainer(type(material)).preferredSuffix
filename = metadata["id"] + "." + suffix
try:
archive.writestr(filename, material.serialize())
except OSError as e:
Logger.log("e", f"An error has occurred while writing the material \'{metadata['id']}\' in the file \'{filename}\': {e}.")
exportUploadStatusChanged = pyqtSignal()
@pyqtProperty(str, notify = exportUploadStatusChanged)
def exportUploadStatus(self) -> str:
return self._export_upload_status
@pyqtSlot()
def exportUpload(self) -> None:
"""
Export all materials and upload them to the user's account.
"""
self._export_upload_status = "uploading"
self.exportUploadStatusChanged.emit()
job = UploadMaterialsJob(self)
job.uploadProgressChanged.connect(self._onUploadProgressChanged)
job.uploadCompleted.connect(self.exportUploadCompleted)
job.start()
def _onUploadProgressChanged(self, progress: float, printers_status: Dict[str, str]):
self.setExportProgress(progress)
self.setPrinterStatus(printers_status)
def exportUploadCompleted(self, job_result: UploadMaterialsJob.Result, job_error: Optional[Exception]):
if not self.sync_all_dialog: # Shouldn't get triggered before the dialog is open, but better to check anyway.
return
if job_result == UploadMaterialsJob.Result.FAILED:
if isinstance(job_error, UploadMaterialsError):
self.sync_all_dialog.setProperty("syncStatusText", catalog.i18nc("@text", "Error sending materials to the Digital Factory:") + " " + str(job_error))
else: # Could be "None"
self.sync_all_dialog.setProperty("syncStatusText", catalog.i18nc("@text", "Unknown error."))
self._export_upload_status = "error"
else:
self._export_upload_status = "success"
self.exportUploadStatusChanged.emit()
exportProgressChanged = pyqtSignal(float)
def setExportProgress(self, progress: float) -> None:
self._export_progress = progress
self.exportProgressChanged.emit(self._export_progress)
@pyqtProperty(float, fset = setExportProgress, notify = exportProgressChanged)
def exportProgress(self) -> float:
return self._export_progress
printerStatusChanged = pyqtSignal()
def setPrinterStatus(self, new_status: Dict[str, str]) -> None:
self._printer_status = new_status
self.printerStatusChanged.emit()
@pyqtProperty("QVariantMap", fset = setPrinterStatus, notify = printerStatusChanged)
def printerStatus(self) -> Dict[str, str]:
return self._printer_status
def reset(self) -> None:
self.setPrinterStatus({})
self.setExportProgress(0.0)
self._export_upload_status = "idle"
self.exportUploadStatusChanged.emit()

View File

@ -1,9 +1,15 @@
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtNetwork import QNetworkRequest
from UM.Logger import Logger
from UM.TaskManagement.HttpRequestScope import DefaultUserAgentScope
from cura.API import Account
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from cura.CuraApplication import CuraApplication
from cura.API.Account import Account
class UltimakerCloudScope(DefaultUserAgentScope):
@ -12,7 +18,7 @@ class UltimakerCloudScope(DefaultUserAgentScope):
Also add the user agent headers (see DefaultUserAgentScope).
"""
def __init__(self, application: CuraApplication):
def __init__(self, application: "CuraApplication"):
super().__init__(application)
api = application.getCuraAPI()
self._account = api.account # type: Account

View File

@ -50,7 +50,7 @@ do
echo "Found Uranium branch [${URANIUM_BRANCH}]."
break
else
echo "Could not find Uranium banch [${URANIUM_BRANCH}], try next."
echo "Could not find Uranium branch [${URANIUM_BRANCH}], try next."
fi
done

View File

@ -8,7 +8,7 @@ The build volume draws a cube (for rectangular build plates) that represents the
The build volume also draws a grid underneath the build volume. The grid features 1cm lines which allows the user to roughly estimate how big its print is or the distance between prints. It also features a finer 1mm line pattern within that grid. The grid is drawn as a single quad. This quad is then sent to the graphical card with a specialised shader which draws the grid pattern.
For elliptical build plates, the volume bounds are drawn as two circles, one at the top and one at the bottom of the available height. The build plate grid is drawn as a tesselated circle, but with the same shader.
For elliptical build plates, the volume bounds are drawn as two circles, one at the top and one at the bottom of the available height. The build plate grid is drawn as a tessellated circle, but with the same shader.
Disallowed areas
----

View File

@ -428,6 +428,7 @@ class CuraEngineBackend(QObject, Backend):
"Unable to slice with the current settings. The following settings have errors: {0}").format(", ".join(error_labels)),
title = catalog.i18nc("@info:title", "Unable to slice"),
message_type = Message.MessageType.WARNING)
Logger.warning(f"Unable to slice with the current settings. The following settings have errors: {', '.join(error_labels)}")
self._error_message.show()
self.setState(BackendState.Error)
self.backendError.emit(job)
@ -454,6 +455,7 @@ class CuraEngineBackend(QObject, Backend):
"Unable to slice due to some per-model settings. The following settings have errors on one or more models: {error_labels}").format(error_labels = ", ".join(errors.values())),
title = catalog.i18nc("@info:title", "Unable to slice"),
message_type = Message.MessageType.WARNING)
Logger.warning(f"Unable to slice due to per-object settings. The following settings have errors on one or more models: {', '.join(errors.values())}")
self._error_message.show()
self.setState(BackendState.Error)
self.backendError.emit(job)
@ -647,7 +649,7 @@ class CuraEngineBackend(QObject, Backend):
for node in DepthFirstIterator(self._scene.getRoot()):
if node.callDecoration("getLayerData"):
if not build_plate_numbers or node.callDecoration("getBuildPlateNumber") in build_plate_numbers:
# We can asume that all nodes have a parent as we're looping through the scene (and filter out root)
# We can assume that all nodes have a parent as we're looping through the scene (and filter out root)
cast(SceneNode, node.getParent()).removeChild(node)
def markSliceAll(self) -> None:

View File

@ -1,4 +1,4 @@
# Copyright (c) 2020 Ultimaker B.V.
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import numpy
@ -195,7 +195,7 @@ class StartSliceJob(Job):
# Remove old layer data.
for node in DepthFirstIterator(self._scene.getRoot()):
if node.callDecoration("getLayerData") and node.callDecoration("getBuildPlateNumber") == self._build_plate_number:
# Singe we walk through all nodes in the scene, they always have a parent.
# Since we walk through all nodes in the scene, they always have a parent.
cast(SceneNode, node.getParent()).removeChild(node)
break
@ -353,10 +353,19 @@ class StartSliceJob(Job):
result[key] = stack.getProperty(key, "value")
Job.yieldThread()
result["print_bed_temperature"] = result["material_bed_temperature"] # Renamed settings.
# Material identification in addition to non-human-readable GUID
result["material_id"] = stack.material.getMetaDataEntry("base_file", "")
result["material_type"] = stack.material.getMetaDataEntry("material", "")
result["material_name"] = stack.material.getMetaDataEntry("name", "")
result["material_brand"] = stack.material.getMetaDataEntry("brand", "")
# Renamed settings.
result["print_bed_temperature"] = result["material_bed_temperature"]
result["print_temperature"] = result["material_print_temperature"]
result["travel_speed"] = result["speed_travel"]
result["time"] = time.strftime("%H:%M:%S") #Some extra settings.
#Some extra settings.
result["time"] = time.strftime("%H:%M:%S")
result["date"] = time.strftime("%d-%m-%Y")
result["day"] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][int(time.strftime("%w"))]
result["initial_extruder_nr"] = CuraApplication.getInstance().getExtruderManager().getInitialExtruderNr()
@ -455,9 +464,9 @@ class StartSliceJob(Job):
bed_temperature_settings = ["material_bed_temperature", "material_bed_temperature_layer_0"]
pattern = r"\{(%s)(,\s?\w+)?\}" % "|".join(bed_temperature_settings) # match {setting} as well as {setting, extruder_nr}
settings["material_bed_temp_prepend"] = re.search(pattern, start_gcode) == None
print_temperature_settings = ["material_print_temperature", "material_print_temperature_layer_0", "default_material_print_temperature", "material_initial_print_temperature", "material_final_print_temperature", "material_standby_temperature"]
print_temperature_settings = ["material_print_temperature", "material_print_temperature_layer_0", "default_material_print_temperature", "material_initial_print_temperature", "material_final_print_temperature", "material_standby_temperature", "print_temperature"]
pattern = r"\{(%s)(,\s?\w+)?\}" % "|".join(print_temperature_settings) # match {setting} as well as {setting, extruder_nr}
settings["material_print_temp_prepend"] = re.search(pattern, start_gcode) == None
settings["material_print_temp_prepend"] = re.search(pattern, start_gcode) is None
# Replace the setting tokens in start and end g-code.
# Use values from the first used extruder by default so we get the expected temperatures

View File

@ -2,7 +2,7 @@
"name": "Ultimaker Digital Library",
"author": "Ultimaker B.V.",
"description": "Connects to the Digital Library, allowing Cura to open files from and save files to the Digital Library.",
"version": "1.0.0",
"version": "1.1.0",
"api": 7,
"i18n-catalog": "cura"
}

View File

@ -67,10 +67,12 @@ class DigitalFactoryApiClient:
def callbackWrap(response: Optional[Any] = None, *args, **kwargs) -> None:
if (response is not None and isinstance(response, DigitalFactoryFeatureBudgetResponse) and
response.library_max_private_projects is not None):
callback(
response.library_max_private_projects == -1 or # Note: -1 is unlimited
response.library_max_private_projects > 0)
# A user has DF access when library_max_private_projects is either -1 (unlimited) or bigger then 0
has_access = response.library_max_private_projects == -1 or response.library_max_private_projects > 0
callback(has_access)
self._library_max_private_projects = response.library_max_private_projects
# update the account with the additional user rights
self._account.updateAdditionalRight(df_access = has_access)
else:
Logger.warning(f"Digital Factory: Response is not a feature budget, likely an error: {str(response)}")
callback(False)

View File

@ -8,6 +8,8 @@ from UM.Logger import Logger
from UM.OutputDevice import OutputDeviceError
from UM.OutputDevice.ProjectOutputDevice import ProjectOutputDevice
from UM.Scene.SceneNode import SceneNode
from UM.Version import Version
from cura import ApplicationMetadata
from cura.API import Account
from cura.CuraApplication import CuraApplication
from .DigitalFactoryController import DigitalFactoryController
@ -107,7 +109,8 @@ class DigitalFactoryOutputDevice(ProjectOutputDevice):
def _onWriteStarted(self, new_name: Optional[str] = None) -> None:
self._writing = True
if new_name:
if new_name and Version(ApplicationMetadata.CuraSDKVersion) >= Version("7.8.0"):
# setLastOutputName is only supported in sdk version 7.8.0 and up
self.setLastOutputName(new_name) # On saving, the user can change the name, this should propagate.
self.writeStarted.emit(self)

View File

@ -428,7 +428,7 @@ class FlavorParser:
G = self._getInt(line, "G")
if G is not None:
# When find a movement, the new posistion is calculated and added to the current_path, but
# When find a movement, the new position is calculated and added to the current_path, but
# don't need to create a polygon until the end of the layer
current_position = self.processGCode(G, line, current_position, current_path)
continue

View File

@ -3,7 +3,7 @@
from . import FlavorParser
# This parser is intented for interpret the Marlin/Sprinter Firmware flavor
# This parser is intended to interpret the Marlin/Sprinter Firmware flavor
class MarlinFlavorParser(FlavorParser.FlavorParser):
def __init__(self):

View File

@ -312,7 +312,7 @@ Item
}
}
// Specialty provider that only watches global_inherits (we cant filter on what property changed we get events
// Specialty provider that only watches global_inherits (we can't filter on what property changed we get events
// so we bypass that to make a dedicated provider).
UM.SettingPropertyProvider
{

View File

@ -100,7 +100,7 @@ UM.Dialog
{
id: loader
width: parent.width
width: listview.width
height: model.type != undefined ? UM.Theme.getSize("section").height : 0
property var definition: model

View File

@ -403,7 +403,7 @@ UM.Dialog
storeIndex: 0
}
// Specialty provider that only watches global_inherits (we cant filter on what property changed we get events
// Specialty provider that only watches global_inherits (we can't filter on what property changed we get events
// so we bypass that to make a dedicated provider).
UM.SettingPropertyProvider
{

View File

@ -9,7 +9,7 @@
# Modified by Ricardo Gomez, ricardoga@otulook.com, to add Bed Temperature and make it work with Cura_13.06.04+
# Modified by Stefan Heule, Dim3nsioneer@gmx.ch since V3.0 (see changelog below)
# Modified by Jaime van Kessel (Ultimaker), j.vankessel@ultimaker.com to make it work for 15.10 / 2.x
# Modified by Ruben Dulek (Ultimaker), r.dulek@ultimaker.com, to debug.
# Modified by Ghostkeeper (Ultimaker), rubend@tutanota.com, to debug.
# Modified by Wes Hanney, https://github.com/novamxd, Retract Length + Speed, Clean up
# history / changelog:
@ -31,7 +31,7 @@
# V4.9.93: Minor bugfixes (input settings) / documentation
# V4.9.94: Bugfix Combobox-selection; remove logger
# V5.0: Bugfix for fall back after one layer and doubled G0 commands when using print speed tweak, Initial version for Cura 2.x
# V5.0.1: Bugfix for calling unknown property 'bedTemp' of previous settings storage and unkown variable 'speed'
# V5.0.1: Bugfix for calling unknown property 'bedTemp' of previous settings storage and unknown variable 'speed'
# V5.1: API Changes included for use with Cura 2.2
# V5.2.0: Wes Hanney. Added support for changing Retract Length and Speed. Removed layer spread option. Fixed issue of cumulative ChangeZ
# mods so they can now properly be stacked on top of each other. Applied code refactoring to clean up various coding styles. Added comments.
@ -657,7 +657,7 @@ class ChangeAtZProcessor:
# Indicates if the user has opted for linear move retractions or firmware retractions
linearRetraction = True
# Indicates if we're targetting by layer or height value
# Indicates if we're targeting by layer or height value
targetByLayer = True
# Indicates if we have injected our changed values for the given layer yet
@ -1079,7 +1079,7 @@ class ChangeAtZProcessor:
else:
modified_gcode += line + "\n"
# if we're targetting by layer we want to add our values just after the layer label
# if we're targeting by layer we want to add our values just after the layer label
if ";LAYER:" in line:
modified_gcode += self.getInjectCode()
@ -1367,11 +1367,11 @@ class ChangeAtZProcessor:
# handle extruder temp changes
if command.command == "M104" or command.command == "M109":
# get our tempurature
tempurature = command.getArgumentAsFloat("S")
# get our temperature
temperature = command.getArgumentAsFloat("S")
# don't bother if we don't have a tempurature
if tempurature is None:
# don't bother if we don't have a temperature
if temperature is None:
return
# get our extruder, default to extruder one
@ -1379,10 +1379,10 @@ class ChangeAtZProcessor:
# set our extruder temp based on the extruder
if extruder is None or extruder == 0:
self.lastValues["extruderOne"] = tempurature
self.lastValues["extruderOne"] = temperature
if extruder is None or extruder == 1:
self.lastValues["extruderTwo"] = tempurature
self.lastValues["extruderTwo"] = temperature
# move to the next command
return
@ -1401,10 +1401,10 @@ class ChangeAtZProcessor:
if command.command == "M221":
# get our flow rate
tempurature = command.getArgumentAsFloat("S")
temperature = command.getArgumentAsFloat("S")
# don't bother if we don't have a flow rate (for some reason)
if tempurature is None:
if temperature is None:
return
# get our extruder, default to global
@ -1412,11 +1412,11 @@ class ChangeAtZProcessor:
# set our extruder temp based on the extruder
if extruder is None:
self.lastValues["flowrate"] = tempurature
self.lastValues["flowrate"] = temperature
elif extruder == 1:
self.lastValues["flowrateOne"] = tempurature
self.lastValues["flowrateOne"] = temperature
elif extruder == 1:
self.lastValues["flowrateTwo"] = tempurature
self.lastValues["flowrateTwo"] = temperature
# move to the next command
return

View File

@ -5,7 +5,7 @@
# Description: This plugin shows custom messages about your print on the Status bar...
# Please look at the 3 options
# - Scolling (SCROLL_LONG_FILENAMES) if enabled in Marlin and you arent printing a small item select this option.
# - Scrolling (SCROLL_LONG_FILENAMES) if enabled in Marlin and you aren't printing a small item select this option.
# - Name: By default it will use the name generated by Cura (EG: TT_Test_Cube) - Type a custom name in here
# - Max Layer: Enabling this will show how many layers are in the entire print (EG: Layer 1 of 265!)

View File

@ -7,6 +7,8 @@
from typing import List
from ..Script import Script
from UM.Application import Application #To get the current printer's settings.
class FilamentChange(Script):
_layer_keyword = ";LAYER:"
@ -81,10 +83,51 @@ class FilamentChange(Script):
"type": "float",
"default_value": 0,
"minimum_value": 0
},
"retract_method":
{
"label": "Retract method",
"description": "The gcode variant to use for retract.",
"type": "enum",
"options": {"U": "Marlin (M600 U)", "L": "Reprap (M600 L)"},
"default_value": "U",
"value": "\\\"L\\\" if machine_gcode_flavor==\\\"RepRap (RepRap)\\\" else \\\"U\\\"",
"enabled": "not firmware_config"
},
"machine_gcode_flavor":
{
"label": "G-code flavor",
"description": "The type of g-code to be generated. This setting is controlled by the script and will not be visible.",
"type": "enum",
"options":
{
"RepRap (Marlin/Sprinter)": "Marlin",
"RepRap (Volumetric)": "Marlin (Volumetric)",
"RepRap (RepRap)": "RepRap",
"UltiGCode": "Ultimaker 2",
"Griffin": "Griffin",
"Makerbot": "Makerbot",
"BFB": "Bits from Bytes",
"MACH3": "Mach3",
"Repetier": "Repetier"
},
"default_value": "RepRap (Marlin/Sprinter)",
"enabled": "false"
}
}
}"""
## Copy machine name and gcode flavor from global stack so we can use their value in the script stack
def initialize(self) -> None:
super().initialize()
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack is None or self._instance is None:
return
for key in ["machine_gcode_flavor"]:
self._instance.setProperty(key, "value", global_container_stack.getProperty(key, "value"))
def execute(self, data: List[str]):
"""Inserts the filament change g-code at specific layer numbers.
@ -106,7 +149,10 @@ class FilamentChange(Script):
color_change = color_change + (" E%.2f" % initial_retract)
if later_retract is not None and later_retract > 0.:
color_change = color_change + (" L%.2f" % later_retract)
# Reprap uses 'L': https://reprap.org/wiki/G-code#M600:_Filament_change_pause
# Marlin uses 'U' https://marlinfw.org/docs/gcode/M600.html
retract_method = self.getSettingValueByKey("retract_method")
color_change = color_change + (" %s%.2f" % (retract_method, later_retract))
if x_pos is not None:
color_change = color_change + (" X%.2f" % x_pos)

View File

@ -458,7 +458,7 @@ class PauseAtHeight(Script):
# Optionally extrude material
if extrude_amount != 0:
prepend_gcode += self.putValue(G = 1, E = extrude_amount, F = 200) + "\n"
prepend_gcode += self.putValue(G = 1, E = extrude_amount, F = 200) + "; Extra extrude after the unpause\n"
prepend_gcode += self.putValue("@info wait for cleaning nozzle from previous filament") + "\n"
prepend_gcode += self.putValue("@pause remove the waste filament from parking area and press continue printing") + "\n"
@ -495,7 +495,7 @@ class PauseAtHeight(Script):
# Optionally extrude material
if extrude_amount != 0:
prepend_gcode += self.putValue(G = 1, E = extrude_amount, F = extrude_speed * 60) + "\n"
prepend_gcode += self.putValue(G = 1, E = extrude_amount, F = extrude_speed * 60) + "; Extra extrude after the unpause\n"
# and retract again, the properly primes the nozzle
# when changing filament.

View File

@ -1,4 +1,4 @@
# Copyright (c) 2017 Ruben Dulek
# Copyright (c) 2017 Ghostkeeper
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
import re #To perform the search and replace.

View File

@ -195,7 +195,7 @@ class Stretcher:
i.e. it is a travel move
"""
if i_pos == 0:
return True # Begining a layer always breaks filament (for simplicity)
return True # Beginning a layer always breaks filament (for simplicity)
step = layer_steps[i_pos]
prev_step = layer_steps[i_pos - 1]
if step.step_e != prev_step.step_e:

View File

@ -136,7 +136,7 @@ class RemovableDriveOutputDevice(OutputDevice):
self._stream.close()
self._stream = None
except:
Logger.logException("w", "An execption occured while trying to write to removable drive.")
Logger.logException("w", "An exception occurred while trying to write to removable drive.")
message = Message(catalog.i18nc("@info:status", "Could not save to removable drive {0}: {1}").format(self.getName(),str(job.getError())),
title = catalog.i18nc("@info:title", "Error"),
message_type = Message.MessageType.ERROR)

View File

@ -48,7 +48,7 @@ class RemovableDrivePlugin(OutputDevicePlugin):
result = False
if result:
Logger.log("i", "Succesfully ejected the device")
Logger.log("i", "Successfully ejected the device")
return result
def performEjectDevice(self, device):

View File

@ -187,7 +187,7 @@ Item
{
sliderRoot.manuallyChanged = true
// don't allow the lower handle to be heigher than the upper handle
// don't allow the lower handle to be higher than the upper handle
if (lowerHandle.y - (y + height) < sliderRoot.minimumRangeHandleSize)
{
lowerHandle.y = y + height + sliderRoot.minimumRangeHandleSize
@ -300,7 +300,7 @@ Item
// don't allow the upper handle to be lower than the lower handle
if (y - (upperHandle.y + upperHandle.height) < sliderRoot.minimumRangeHandleSize)
{
upperHandle.y = y - (upperHandle.heigth + sliderRoot.minimumRangeHandleSize)
upperHandle.y = y - (upperHandle.height + sliderRoot.minimumRangeHandleSize)
}
// update the range handle

View File

@ -59,7 +59,7 @@ UM.PointingRectangle {
text: sliderLabelRoot.value + startFrom // the current handle value, add 1 because layers is an array
horizontalAlignment: TextInput.AlignHCenter
// key bindings, work when label is currenctly focused (active handle in LayerSlider)
// key bindings, work when label is currently focused (active handle in LayerSlider)
Keys.onUpPressed: sliderLabelRoot.setValue(sliderLabelRoot.value + ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
Keys.onDownPressed: sliderLabelRoot.setValue(sliderLabelRoot.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))

View File

@ -190,11 +190,11 @@ Item
}
}
// Scrolls trough Z layers
// Scrolls through Z layers
LayerSlider
{
property var preferredHeight: UM.Theme.getSize("slider_layerview_size").height
property double heightMargin: UM.Theme.getSize("default_margin").height * 3 // extra margin to accomodate layer number tooltips
property double heightMargin: UM.Theme.getSize("default_margin").height * 3 // extra margin to accommodate layer number tooltips
property double layerSliderSafeHeight: layerSliderSafeYMax - layerSliderSafeYMin
id: layerSlider

View File

@ -130,6 +130,7 @@ class SliceInfo(QObject, Extension):
data["cura_version"] = self._application.getVersion()
data["cura_build_type"] = ApplicationMetadata.CuraBuildType
org_id = user_profile.get("organization_id", None) if user_profile else None
data["is_logged_in"] = self._application.getCuraAPI().account.isLoggedIn
data["organization_id"] = org_id if org_id else None
data["subscriptions"] = user_profile.get("subscriptions", []) if user_profile else []

View File

@ -7,6 +7,7 @@
<b>Intent Profile:</b> Default<br/>
<b>Quality Profile:</b> Fast<br/>
<b>Using Custom Settings:</b> No<br/>
<b>Is Logged In:</b> Yes<br/>
<b>Organization ID (if any):</b> ABCDefGHIjKlMNOpQrSTUvYxWZ0-1234567890abcDE=<br/>
<b>Subscriptions (if any):</b>
<ul>

View File

@ -37,7 +37,7 @@ class CloudPackageChecker(QObject):
self._i18n_catalog = i18nCatalog("cura")
self._sdk_version = ApplicationMetadata.CuraSDKVersion
self._last_notified_packages = set() # type: Set[str]
"""Packages for which a notification has been shown. No need to bother the user twice fo equal content"""
"""Packages for which a notification has been shown. No need to bother the user twice for equal content"""
# This is a plugin, so most of the components required are not ready when
# this is initialized. Therefore, we wait until the application is ready.

View File

@ -6,7 +6,7 @@ from typing import Dict, List, Any
from PyQt5.QtNetwork import QNetworkReply
from UM import i18n_catalog
from UM.i18n import i18nCatalog
from UM.Logger import Logger
from UM.Message import Message
from UM.Signal import Signal
@ -15,6 +15,8 @@ from cura.CuraApplication import CuraApplication
from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope
from .SubscribedPackagesModel import SubscribedPackagesModel
i18n_catalog = i18nCatalog("cura")
class DownloadPresenter:
"""Downloads a set of packages from the Ultimaker Cloud Marketplace
@ -90,7 +92,7 @@ class DownloadPresenter:
lifetime = 0,
use_inactivity_timer = False,
progress = 0.0,
title = i18n_catalog.i18nc("@info:title", "Changes detected from your Ultimaker account", ))
title = i18n_catalog.i18nc("@info:title", "Changes detected from your Ultimaker account"))
def _onFinished(self, package_id: str, reply: QNetworkReply) -> None:
self._progress[package_id]["received"] = self._progress[package_id]["total"]

View File

@ -1,3 +1,6 @@
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import os
from collections import OrderedDict
from typing import Dict, Optional, List, Any
@ -95,7 +98,11 @@ class LicensePresenter(QObject):
for package_id, item in packages.items():
item["package_id"] = package_id
try:
item["licence_content"] = self._package_manager.getPackageLicense(item["package_path"])
except EnvironmentError as e:
Logger.error(f"Could not open downloaded package {package_id} to read license file! {type(e)} - {e}")
continue # Skip this package.
if item["licence_content"] is None:
# Implicitly accept when there is no license
item["accepted"] = True

View File

@ -1,4 +1,4 @@
# Copyright (c) 2020 Ultimaker B.V.
# Copyright (c) 2021 Ultimaker B.V.
# Toolbox is released under the terms of the LGPLv3 or higher.
import json
@ -542,7 +542,7 @@ class Toolbox(QObject, Extension):
# Make API Calls
# --------------------------------------------------------------------------
def _makeRequestByType(self, request_type: str) -> None:
Logger.log("d", "Requesting [%s] metadata from server.", request_type)
Logger.debug(f"Requesting {request_type} metadata from server.")
url = self._request_urls[request_type]
callback = lambda r, rt = request_type: self._onDataRequestFinished(rt, r)
@ -554,7 +554,7 @@ class Toolbox(QObject, Extension):
@pyqtSlot(str)
def startDownload(self, url: str) -> None:
Logger.log("i", "Attempting to download & install package from %s.", url)
Logger.info(f"Attempting to download & install package from {url}.")
callback = lambda r: self._onDownloadFinished(r)
error_callback = lambda r, e: self._onDownloadFailed(r, e)
@ -572,7 +572,7 @@ class Toolbox(QObject, Extension):
@pyqtSlot()
def cancelDownload(self) -> None:
Logger.log("i", "User cancelled the download of a package. request %s", self._download_request_data)
Logger.info(f"User cancelled the download of a package. request {self._download_request_data}")
if self._download_request_data is not None:
self._application.getHttpRequestManager().abortRequest(self._download_request_data)
self._download_request_data = None
@ -585,7 +585,7 @@ class Toolbox(QObject, Extension):
# Handlers for Network Events
# --------------------------------------------------------------------------
def _onDataRequestError(self, request_type: str, reply: "QNetworkReply", error: "QNetworkReply.NetworkError") -> None:
Logger.log("e", "Request [%s] failed due to error [%s]: %s", request_type, error, reply.errorString())
Logger.error(f"Request {request_type} failed due to error {error}: {reply.errorString()}")
self.setViewPage("errored")
def _onDataRequestFinished(self, request_type: str, reply: "QNetworkReply") -> None:
@ -682,9 +682,13 @@ class Toolbox(QObject, Extension):
if not package_info:
Logger.log("w", "Package file [%s] was not a valid CuraPackage.", file_path)
return
license_content = self._package_manager.getPackageLicense(file_path)
package_id = package_info["package_id"]
try:
license_content = self._package_manager.getPackageLicense(file_path)
except EnvironmentError as e:
Logger.error(f"Could not open downloaded package {package_id} to read license file! {type(e)} - {e}")
return
if license_content is not None:
# get the icon url for package_id, make sure the result is a string, never None
icon_url = next((x["icon_url"] for x in self.packagesModel.items if x["id"] == package_id), None) or ""

View File

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides support for reading Ultimaker Format Packages.",
"supported_sdk_versions": ["7.7.0"],
"supported_sdk_versions": ["7.8.0"],
"i18n-catalog": "cura"
}

View File

@ -16,7 +16,7 @@ Item
{
id: base
// The print job which all other information is dervied from
// The print job which all other information is derived from
property var printJob: null
width: childrenRect.width

View File

@ -0,0 +1,353 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
viewBox="0 0 274.75 126.24"
version="1.1"
id="svg425"
sodipodi:docname="CloudPlatform.svg"
width="274.75"
height="126.24"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
<metadata
id="metadata429">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1200"
id="namedview427"
showgrid="false"
fit-margin-left="1"
fit-margin-bottom="1"
fit-margin-top="1"
fit-margin-right="1"
inkscape:zoom="2.593819"
inkscape:cx="115.77157"
inkscape:cy="14.444977"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg425" />
<defs
id="defs332">
<style
id="style330">.cls-1{fill:#f3f8fe;}.cls-2{fill:none;stroke:#061884;stroke-miterlimit:10;}.cls-3{fill:#061884;}.cls-4,.cls-6{fill:#fff;}.cls-4{fill-rule:evenodd;}.cls-5{fill:#dde9fd;}.cls-7{fill:#c5dbfb;}</style>
</defs>
<g
id="Layer_2"
data-name="Layer 2"
transform="translate(-28.84,-11.189998)">
<path
class="cls-1"
d="M 71.93,79.82 H 49.62 a 4.12,4.12 0 0 0 -4.13,4.11 v 47.55 a 4.13,4.13 0 0 0 4.13,4.12 h 22.31 a 4.13,4.13 0 0 0 4.13,-4.12 V 83.93 a 4.12,4.12 0 0 0 -4.13,-4.11 z m 2.18,51 a 2.82,2.82 0 0 1 -2.82,2.82 h -21 a 2.83,2.83 0 0 1 -2.82,-2.82 V 84.58 a 2.84,2.84 0 0 1 2.82,-2.83 h 5.92 a 1.45,1.45 0 0 0 1.45,1.46 h 6.3 a 1.46,1.46 0 0 0 1.46,-1.46 h 5.91 a 2.83,2.83 0 0 1 2.82,2.83 z"
id="path334"
inkscape:connector-curvature="0"
style="fill:#f3f8fe" />
<path
class="cls-2"
d="M 71.93,79.82 H 49.62 a 4.12,4.12 0 0 0 -4.13,4.11 v 47.55 a 4.13,4.13 0 0 0 4.13,4.12 h 22.31 a 4.13,4.13 0 0 0 4.13,-4.12 V 83.93 a 4.12,4.12 0 0 0 -4.13,-4.11 z"
id="path336"
inkscape:connector-curvature="0"
style="fill:none;stroke:#061884;stroke-miterlimit:10" />
<path
class="cls-3"
d="m 63.2,81 h -4.85 a 0.5,0.5 0 1 0 0,1 h 4.85 a 0.5,0.5 0 0 0 0,-1 z"
id="path338"
inkscape:connector-curvature="0"
style="fill:#061884" />
<path
class="cls-4"
d="m 74.11,84.58 v 46.26 a 2.82,2.82 0 0 1 -2.82,2.82 h -21 a 2.83,2.83 0 0 1 -2.82,-2.82 V 84.58 a 2.84,2.84 0 0 1 2.82,-2.83 h 5.92 a 1.45,1.45 0 0 0 1.45,1.46 h 6.3 a 1.46,1.46 0 0 0 1.46,-1.46 h 5.91 a 2.83,2.83 0 0 1 2.78,2.83 z"
id="path340"
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-rule:evenodd" />
<rect
class="cls-5"
x="50.32"
y="125.88"
width="19.91"
height="4.7399998"
id="rect342"
style="fill:#dde9fd" />
<rect
class="cls-5"
x="50.32"
y="85.959999"
width="19.91"
height="1.9"
rx="0.94999999"
id="rect344"
style="fill:#dde9fd" />
<rect
class="cls-5"
x="50.32"
y="114.4"
width="10.43"
height="1.9"
rx="0.94999999"
id="rect346"
style="fill:#dde9fd" />
<rect
class="cls-5"
x="50.32"
y="117.25"
width="10.43"
height="1.9"
rx="0.94999999"
id="rect348"
style="fill:#dde9fd" />
<path
class="cls-1"
d="m 291.5,135.38 a 5.12,5.12 0 0 0 5.11,-5.11 v -0.38 a 0.38,0.38 0 0 0 -0.37,-0.37 h -103.9 a 0.37,0.37 0 0 0 -0.36,0.37 v 0.38 a 5.11,5.11 0 0 0 5.1,5.11 z"
id="path350"
inkscape:connector-curvature="0"
style="fill:#f3f8fe" />
<path
class="cls-3"
d="m 296.24,129.89 h -103.9 v 0.38 0 a 4.74,4.74 0 0 0 4.74,4.74 h 94.42 a 4.74,4.74 0 0 0 4.74,-4.74 v -0.38 m 0,-0.73 a 0.73,0.73 0 0 1 0.73,0.73 v 0.38 a 5.47,5.47 0 0 1 -5.47,5.47 h -94.42 a 5.47,5.47 0 0 1 -5.47,-5.47 v -0.38 a 0.73,0.73 0 0 1 0.73,-0.73 z"
id="path352"
inkscape:connector-curvature="0"
style="fill:#061884" />
<path
class="cls-3"
d="m 235.51,129.16 a 2.93,2.93 0 0 0 2.93,2.93 h 11.71 a 2.93,2.93 0 0 0 2.92,-2.93 z"
id="path354"
inkscape:connector-curvature="0"
style="fill:#061884" />
<path
class="cls-1"
d="M 287.83,129.52 V 71.36 a 2.56,2.56 0 0 0 -2.56,-2.56 h -81.95 a 2.56,2.56 0 0 0 -2.56,2.56 v 58.16 z"
id="path356"
inkscape:connector-curvature="0"
style="fill:#f3f8fe" />
<path
class="cls-2"
d="M 287.83,129.52 V 71.36 a 2.56,2.56 0 0 0 -2.56,-2.56 h -81.95 a 2.56,2.56 0 0 0 -2.56,2.56 v 58.16"
id="path358"
inkscape:connector-curvature="0"
style="fill:none;stroke:#061884;stroke-miterlimit:10" />
<path
class="cls-6"
d="m 284.17,128.79 v -56 a 0.36,0.36 0 0 0 -0.37,-0.36 h -79 a 0.36,0.36 0 0 0 -0.36,0.36 v 56 z"
id="path360"
inkscape:connector-curvature="0"
style="fill:#ffffff" />
<path
class="cls-1"
d="m 283.8,72.82 h -79 v 55.61 h 79 V 72.82 m 0.74,0 v 56.34 H 204.05 V 72.82 a 0.73,0.73 0 0 1 0.73,-0.73 h 79 a 0.74,0.74 0 0 1 0.76,0.73 z"
id="path362"
inkscape:connector-curvature="0"
style="fill:#f3f8fe" />
<path
class="cls-2"
d="M 204.11,129.57 V 73.86 a 1.64,1.64 0 0 1 1.64,-1.64 H 283 a 1.64,1.64 0 0 1 1.64,1.64 v 55.71"
id="path364"
inkscape:connector-curvature="0"
style="fill:none;stroke:#061884;stroke-miterlimit:10" />
<path
class="cls-2"
d="m 291.5,135.38 a 5.12,5.12 0 0 0 5.11,-5.11 v -0.38 a 0.38,0.38 0 0 0 -0.37,-0.37 h -103.9 a 0.37,0.37 0 0 0 -0.36,0.37 v 0.38 a 5.11,5.11 0 0 0 5.1,5.11 z"
id="path366"
inkscape:connector-curvature="0"
style="fill:none;stroke:#061884;stroke-miterlimit:10" />
<path
class="cls-3"
d="m 131.73,12.19 c -3.87,0 -8.7,5.75 -14.75,17.5 -4.63,9 -8.26,18.32 -8.3,18.41 a 0.86443623,0.86443623 0 0 0 1.61,0.63 c 5.46,-14.09 16.24,-36 21.88,-34.77 5.64,1.23 5.35,21.35 3.87,33.76 a 0.86142324,0.86142324 0 1 0 1.71,0.21 c 0.41,-3.45 3.75,-33.71 -5.22,-35.65 a 3.57,3.57 0 0 0 -0.8,-0.09 z"
id="path368"
inkscape:connector-curvature="0"
style="fill:#061884" />
<path
class="cls-3"
d="m 143.87,17.34 a 3.56,3.56 0 0 0 -0.8,0.08 c -9,1.94 -5.63,32.2 -5.22,35.65 a 0.86142324,0.86142324 0 1 0 1.71,-0.21 c -1.48,-12.41 -1.74,-32.55 3.87,-33.76 5.61,-1.21 16.42,20.68 21.88,34.77 a 0.86443623,0.86443623 0 1 0 1.61,-0.63 c 0,-0.09 -3.67,-9.42 -8.3,-18.41 -6.05,-11.75 -10.88,-17.49 -14.75,-17.49 z"
id="path370"
inkscape:connector-curvature="0"
style="fill:#061884" />
<path
class="cls-1"
d="m 178,135.58 a 2.25,2.25 0 0 0 2.24,-2.24 v -84 A 2.3,2.3 0 0 0 178,47 H 94.81 a 2.29,2.29 0 0 0 -2.24,2.29 v 84 a 2.24,2.24 0 0 0 2.24,2.24 h 6 l 0.69,-0.38 c 3.56,-2 3.94,-2.2 8.66,-2.2 h 51.59 c 4.72,0 5.09,0.21 8.66,2.2 l 0.69,0.38 z"
id="path372"
inkscape:connector-curvature="0"
style="fill:#f3f8fe" />
<path
class="cls-3"
d="M 178,47.45 H 94.81 A 1.85,1.85 0 0 0 93,49.31 v 84 0 a 1.81,1.81 0 0 0 1.81,1.81 h 5.93 c 4.15,-2.3 4.37,-2.58 9.46,-2.58 h 51.59 c 5.08,0 5.31,0.28 9.46,2.58 H 178 a 1.81,1.81 0 0 0 1.81,-1.81 v -84 A 1.85,1.85 0 0 0 178,47.45 m 2.67,1.86 v 84 A 2.68,2.68 0 0 1 178,136 h -7 l -0.19,-0.11 -0.59,-0.33 c -3.55,-2 -3.84,-2.14 -8.45,-2.14 H 110.2 c -4.61,0 -4.9,0.16 -8.45,2.14 l -0.59,0.33 -0.2,0.11 h -6.15 a 2.66,2.66 0 0 1 -2.67,-2.67 v -84 a 2.69,2.69 0 0 1 2.67,-2.72 H 178 a 2.7,2.7 0 0 1 2.71,2.7 z"
id="path374"
inkscape:connector-curvature="0"
style="fill:#061884" />
<rect
class="cls-3"
x="111.92"
y="126.55"
width="3.4400001"
height="0.86000001"
id="rect376"
style="fill:#061884" />
<circle
class="cls-3"
cx="102.46"
cy="50.029999"
r="0.86000001"
id="circle378"
style="fill:#061884" />
<circle
class="cls-3"
cx="124.81"
cy="50.029999"
r="0.86000001"
id="circle380"
style="fill:#061884" />
<circle
class="cls-3"
cx="147.17"
cy="50.029999"
r="0.86000001"
id="circle382"
style="fill:#061884" />
<circle
class="cls-3"
cx="169.53"
cy="50.029999"
r="0.86000001"
id="circle384"
style="fill:#061884" />
<circle
class="cls-3"
cx="102.46"
cy="126.55"
r="0.86000001"
id="circle386"
style="fill:#061884" />
<circle
class="cls-3"
cx="169.53"
cy="126.55"
r="0.86000001"
id="circle388"
style="fill:#061884" />
<path
class="cls-6"
d="m 168.52,121.82 a 6.6,6.6 0 0 0 6.6,-6.59 V 60.42 A 3.1,3.1 0 0 0 172,57.34 h -71.19 a 3.08,3.08 0 0 0 -3.08,3.08 v 54.81 a 6.59,6.59 0 0 0 6.6,6.59 z"
id="path390"
inkscape:connector-curvature="0"
style="fill:#ffffff" />
<path
class="cls-1"
d="m 172,57.77 h -71.19 a 2.65,2.65 0 0 0 -2.65,2.65 v 54.81 a 6.16,6.16 0 0 0 6.17,6.16 h 64.19 a 6.18,6.18 0 0 0 6.17,-6.16 V 60.42 A 2.66,2.66 0 0 0 172,57.77 m 3.52,2.65 v 54.81 0 a 7,7 0 0 1 -7,7 h -64.19 a 7,7 0 0 1 -7,-7 V 60.42 a 3.51,3.51 0 0 1 3.51,-3.51 H 172 a 3.52,3.52 0 0 1 3.55,3.51 z"
id="path392"
inkscape:connector-curvature="0"
style="fill:#f3f8fe" />
<path
class="cls-3"
d="m 172,56.91 h -71.19 a 3.51,3.51 0 0 0 -3.51,3.51 v 54.81 a 7,7 0 0 0 7,7 h 64.19 a 7,7 0 0 0 7,-7 V 60.42 A 3.52,3.52 0 0 0 172,56.91 m 4.38,3.51 v 54.81 a 7.9,7.9 0 0 1 -7.89,7.88 h -64.16 a 7.88,7.88 0 0 1 -7.89,-7.88 V 60.42 a 4.37,4.37 0 0 1 4.37,-4.37 H 172 a 4.38,4.38 0 0 1 4.41,4.37 z"
id="path394"
inkscape:connector-curvature="0"
style="fill:#061884" />
<path
class="cls-7"
d="m 146.31,118 h -20.64 v 10.32 h 20.64 V 118 m 0,-0.85 v 0 a 0.83,0.83 0 0 1 0.84,0.83 v 10.32 0 a 0.83,0.83 0 0 1 -0.84,0.83 h -20.66 a 0.84,0.84 0 0 1 -0.84,-0.83 v -10.37 a 0.84,0.84 0 0 1 0.84,-0.83 z"
id="path396"
inkscape:connector-curvature="0"
style="fill:#c5dbfb" />
<path
class="cls-6"
d="m 142.1,65.93 a 1.35,1.35 0 0 0 1.29,-1 L 145,58.77 v -2.29 h -18 v 2.29 l 1.6,6.23 a 1.34,1.34 0 0 0 1.28,1 z"
id="path398"
inkscape:connector-curvature="0"
style="fill:#ffffff" />
<path
class="cls-3"
d="m 144.59,56.91 h -17.2 v 1.8 l 1.61,6.14 a 0.9,0.9 0 0 0 0.87,0.65 h 12.23 a 0.89,0.89 0 0 0 0.87,-0.65 l 1.62,-6.14 v -1.8 m 0.86,-0.86 v 2.78 0.1 l -1.62,6.16 a 1.77,1.77 0 0 1 -1.7,1.27 h -12.25 a 1.78,1.78 0 0 1 -1.7,-1.29 l -1.62,-6.14 v -0.1 -2.78 z"
id="path400"
inkscape:connector-curvature="0"
style="fill:#061884" />
<path
class="cls-6"
d="m 140.19,67.65 h 0.15 a 1.34,1.34 0 0 0 1.17,-1.48 l -0.84,-7.11 h -9.36 l -0.84,7.11 v 0.15 a 1.32,1.32 0 0 0 1.33,1.33 z"
id="path402"
inkscape:connector-curvature="0"
style="fill:#ffffff" />
<path
class="cls-3"
d="m 140.29,59.49 h -8.6 l -0.79,6.73 v 0.1 a 0.9,0.9 0 0 0 0.9,0.9 h 8.49 a 0.9,0.9 0 0 0 0.79,-1 l -0.79,-6.73 m 0.77,-0.86 0.09,0.76 0.79,6.74 a 1.21,1.21 0 0 1 0,0.19 1.76,1.76 0 0 1 -1.76,1.76 h -8.58 a 1.76,1.76 0 0 1 -1.55,-1.95 l 0.79,-6.73 0.09,-0.76 z"
id="path404"
inkscape:connector-curvature="0"
style="fill:#061884" />
<path
class="cls-6"
d="m 147,59.06 a 2.59,2.59 0 0 0 0,-5.16 h -22 a 2.59,2.59 0 0 0 0,5.16 z"
id="path406"
inkscape:connector-curvature="0"
style="fill:#ffffff" />
<path
class="cls-3"
d="m 147,54.33 h -22 a 2,2 0 0 0 -1.92,2.13 2.07,2.07 0 0 0 1.92,2.17 h 22 a 2.07,2.07 0 0 0 1.92,-2.17 2,2 0 0 0 -1.92,-2.13 m 2.78,2.13 a 2.92,2.92 0 0 1 -2.78,3 h -22 a 3,3 0 0 1 0,-6 h 22 a 2.9,2.9 0 0 1 2.75,3 z"
id="path408"
inkscape:connector-curvature="0"
style="fill:#061884" />
<rect
class="cls-3"
x="135.56"
y="54.330002"
width="0.86000001"
height="4.3699999"
id="rect410"
style="fill:#061884" />
<line
class="cls-2"
x1="29.84"
y1="135.92999"
x2="302.59"
y2="135.92999"
id="line412"
style="fill:none;stroke:#061884;stroke-miterlimit:10" />
<polygon
class="cls-5"
points="112.35,101.51 124.06,121.81 147.5,121.81 159.22,101.51 147.5,81.22 124.06,81.22 "
id="polygon414"
style="fill:#dde9fd" />
<polygon
class="cls-5"
points="224.57,103.51 234.68,121.01 254.89,121.01 264.99,103.51 254.89,86.01 234.68,86.01 "
id="polygon416"
style="fill:#dde9fd" />
<path
class="cls-6"
d="m 125.65,117.53 a 0.41,0.41 0 0 0 -0.41,0.4 v 10.37 0 a 0.41,0.41 0 0 0 0.41,0.4 h 20.68 a 0.4,0.4 0 0 0 0.41,-0.4 v -10.37 0 a 0.4,0.4 0 0 0 -0.41,-0.4 z"
id="path418"
inkscape:connector-curvature="0"
style="fill:#ffffff" />
<path
class="cls-3"
d="m 146.33,117.1 h -20.68 a 0.84,0.84 0 0 0 -0.84,0.83 v 10.37 a 0.84,0.84 0 0 0 0.84,0.83 h 20.68 a 0.83,0.83 0 0 0 0.84,-0.83 v -10.37 0 a 0.83,0.83 0 0 0 -0.84,-0.83 m 1.7,0.83 v 10.37 a 1.7,1.7 0 0 1 -1.7,1.69 h -20.68 a 1.7,1.7 0 0 1 -1.7,-1.69 v -10.37 a 1.7,1.7 0 0 1 1.7,-1.69 h 20.68 a 1.7,1.7 0 0 1 1.67,1.69 z"
id="path420"
inkscape:connector-curvature="0"
style="fill:#061884" />
<polygon
class="cls-5"
points="50.22,101.67 55.22,110.33 65.22,110.33 70.22,101.67 65.22,93.01 55.22,93.01 "
id="polygon422"
style="fill:#dde9fd" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="186px" height="57px" viewBox="0 0 186 57" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 52.6 (67491) - http://www.bohemiancoding.com/sketch -->
<title>Cloud_connection-icon</title>
<desc>Created with Sketch.</desc>
<g id="Cloud_connection-icon" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M38.6261428,52.7109865 L7.48755878,52.7109865 C6.85100215,52.7676651 6.21444551,52.994379 5.75149524,53.3911284 L5.40428251,53.7311992 C5.05706981,54.2979841 4.36264439,54.4113411 3.66821896,54.4113411 L0.543304569,54.4113411 C0.369698215,54.4113411 0.196091859,54.2413055 0.196091859,54.0712703 L0.196091859,0.623463283 C0.196091859,0.453427843 0.369698215,0.283392401 0.543304569,0.283392401 L48.3429212,0.283392401 C48.5165273,0.283392401 48.6901338,0.453427843 48.6901338,0.623463283 L48.6901338,26.0155943 C48.4613867,26.0052354 48.2313048,26 48,26 C46.4042274,26 44.8666558,26.2491876 43.4240742,26.7107738 L43.4240742,6.23463283 C43.4240742,5.61116956 42.9032553,5.15774169 42.3245675,5.15774169 L6.50378945,5.15774169 C5.86723281,5.15774169 5.40428251,5.66784803 5.40428251,6.23463283 L5.40428251,41.3186122 C5.40428251,42.9056095 6.73526457,44.2092147 8.35559054,44.2092147 L33.3440862,44.2092147 C34.087979,47.6221969 35.9937272,50.6011835 38.6261428,52.7109865 Z" id="Combined-Shape" fill="#08073F" fill-rule="nonzero"></path>
<path d="M158.961954,52.7109865 L131.487559,52.7109865 C130.851002,52.7676651 130.214446,52.994379 129.751495,53.3911284 L129.404283,53.7311992 C129.05707,54.2979841 128.362644,54.4113411 127.668219,54.4113411 L124.543305,54.4113411 C124.369698,54.4113411 124.196092,54.2413055 124.196092,54.0712703 L124.196092,0.623463283 C124.196092,0.453427843 124.369698,0.283392401 124.543305,0.283392401 L172.342921,0.283392401 C172.516527,0.283392401 172.690134,0.453427843 172.690134,0.623463283 L172.690134,27.0854877 C172.13468,27.0289729 171.570805,27 171,27 C169.770934,27 168.574002,27.1343278 167.424074,27.3886981 L167.424074,6.23463283 C167.424074,5.61116956 166.903255,5.15774169 166.324567,5.15774169 L130.503789,5.15774169 C129.867233,5.15774169 129.404283,5.66784803 129.404283,6.23463283 L129.404283,41.3186122 C129.404283,42.9056095 130.735265,44.2092147 132.355591,44.2092147 L155.096113,44.2092147 C155.462794,47.4493334 156.859805,50.3873861 158.961954,52.7109865 Z" id="Combined-Shape" fill="#08073F" fill-rule="nonzero"></path>
<path d="M171,56 C163.26057,56 157,49.9481159 157,42.5 C157,35.0518841 163.26057,29 171,29 C178.73943,29 185,35.0518841 185,42.5 C185,49.9481159 178.73943,56 171,56 Z M177.416667,40.7546296 C177.233333,39.1569444 175.858333,37.9351852 174.208333,37.9351852 C173.75,37.9351852 173.383333,38.0291667 173.016667,38.2171296 C172.191667,36.9013889 170.725,36.0555556 169.166667,36.0555556 C166.6,36.0555556 164.583333,38.1231482 164.583333,40.7546296 C164.583333,40.7546296 164.583333,40.7546296 164.583333,40.8486111 C163.025,41.0365741 161.833333,42.4462963 161.833333,44.0439815 C161.833333,45.8296296 163.3,47.3333333 165.041667,47.3333333 C166.416667,47.3333333 175.308333,47.3333333 176.958333,47.3333333 C178.7,47.3333333 180.166667,45.8296296 180.166667,44.0439815 C180.166667,42.3523148 178.975,41.0365741 177.416667,40.7546296 Z" id="Combined-Shape" fill="#3282FF" fill-rule="nonzero"></path>
<path d="M48,54 C40.8202983,54 35,48.1797017 35,41 C35,33.8202983 40.8202983,28 48,28 C55.1797017,28 61,33.8202983 61,41 C61,48.1797017 55.1797017,54 48,54 Z M46.862511,41.4631428 L43.8629783,38.6111022 L41.1067187,41.5099007 L47.0308248,47.1427085 L55.8527121,37.698579 L52.9296286,34.9680877 L46.862511,41.4631428 Z" id="Combined-Shape" fill="#3282FF" fill-rule="nonzero"></path>
<path d="M54.5,25 C53.6715729,25 53,24.3284271 53,23.5 C53,22.6715729 53.6715729,22 54.5,22 C55.3284271,22 56,22.6715729 56,23.5 C56,24.3284271 55.3284271,25 54.5,25 Z M78.5,25 C77.6715729,25 77,24.3284271 77,23.5 C77,22.6715729 77.6715729,22 78.5,22 C79.3284271,22 80,22.6715729 80,23.5 C80,24.3284271 79.3284271,25 78.5,25 Z M102.5,25 C101.671573,25 101,24.3284271 101,23.5 C101,22.6715729 101.671573,22 102.5,22 C103.328427,22 104,22.6715729 104,23.5 C104,24.3284271 103.328427,25 102.5,25 Z M62.5,25 C61.6715729,25 61,24.3284271 61,23.5 C61,22.6715729 61.6715729,22 62.5,22 C63.3284271,22 64,22.6715729 64,23.5 C64,24.3284271 63.3284271,25 62.5,25 Z M86.5,25 C85.6715729,25 85,24.3284271 85,23.5 C85,22.6715729 85.6715729,22 86.5,22 C87.3284271,22 88,22.6715729 88,23.5 C88,24.3284271 87.3284271,25 86.5,25 Z M110.5,25 C109.671573,25 109,24.3284271 109,23.5 C109,22.6715729 109.671573,22 110.5,22 C111.328427,22 112,22.6715729 112,23.5 C112,24.3284271 111.328427,25 110.5,25 Z M70.5,25 C69.6715729,25 69,24.3284271 69,23.5 C69,22.6715729 69.6715729,22 70.5,22 C71.3284271,22 72,22.6715729 72,23.5 C72,24.3284271 71.3284271,25 70.5,25 Z M94.5,25 C93.6715729,25 93,24.3284271 93,23.5 C93,22.6715729 93.6715729,22 94.5,22 C95.3284271,22 96,22.6715729 96,23.5 C96,24.3284271 95.3284271,25 94.5,25 Z M118.5,25 C117.671573,25 117,24.3284271 117,23.5 C117,22.6715729 117.671573,22 118.5,22 C119.328427,22 120,22.6715729 120,23.5 C120,24.3284271 119.328427,25 118.5,25 Z" id="Combined-Shape" fill="#3282FF" fill-rule="nonzero"></path>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -1,4 +1,4 @@
# Copyright (c) 2020 Ultimaker B.V.
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import os
@ -16,6 +16,7 @@ from UM.Util import parseBool
from cura.API import Account
from cura.API.Account import SyncState
from cura.CuraApplication import CuraApplication
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry # To update printer metadata with information received about cloud printers.
from cura.Settings.CuraStackBuilder import CuraStackBuilder
from cura.Settings.GlobalStack import GlobalStack
from cura.UltimakerCloud.UltimakerCloudConstants import META_UM_LINKED_TO_ACCOUNT
@ -129,6 +130,8 @@ class CloudOutputDeviceManager:
self._um_cloud_printers[device_id].setMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, True)
self._onDevicesDiscovered(new_clusters)
self._updateOnlinePrinters(all_clusters)
# Hide the current removed_printers_message, if there is any
if self._removed_printers_message:
self._removed_printers_message.actionTriggered.disconnect(self._onRemovedPrintersMessageActionTriggered)
@ -154,6 +157,8 @@ class CloudOutputDeviceManager:
self._syncing = False
self._account.setSyncState(self.SYNC_SERVICE_NAME, SyncState.SUCCESS)
Logger.debug("Synced cloud printers with account.")
def _onGetRemoteClusterFailed(self, reply: QNetworkReply, error: QNetworkReply.NetworkError) -> None:
self._syncing = False
self._account.setSyncState(self.SYNC_SERVICE_NAME, SyncState.ERROR)
@ -255,6 +260,16 @@ class CloudOutputDeviceManager:
message_text = self.i18n_catalog.i18nc("info:status", "Printers added from Digital Factory:") + "<ul>" + device_names + "</ul>"
message.setText(message_text)
def _updateOnlinePrinters(self, printer_responses: Dict[str, CloudClusterResponse]) -> None:
"""
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.
"""
for container_stack in CuraContainerRegistry.getInstance().findContainerStacks(type = "machine"):
cluster_id = container_stack.getMetaDataEntry("um_cloud_cluster_id", "")
if cluster_id in printer_responses:
container_stack.setMetaDataEntry("is_online", printer_responses[cluster_id].is_online)
def _updateOutdatedMachine(self, outdated_machine: GlobalStack, new_cloud_output_device: CloudOutputDevice) -> None:
"""
Update the cloud metadata of a pre-existing machine that is rediscovered (e.g. if the printer was removed and

View File

@ -15,27 +15,26 @@ I18N_CATALOG = i18nCatalog("cura")
class CloudFlowMessage(Message):
def __init__(self, address: str) -> None:
def __init__(self, printer_name: str) -> None:
image_path = os.path.join(
CuraApplication.getInstance().getPluginRegistry().getPluginPath("UM3NetworkPrinting") or "",
"resources", "svg", "cloud-flow-start.svg"
"resources", "svg", "CloudPlatform.svg"
)
super().__init__(
text = I18N_CATALOG.i18nc("@info:status",
"Send and monitor print jobs from anywhere using your Ultimaker account."),
lifetime=0,
dismissable=True,
option_state=False,
image_source=QUrl.fromLocalFile(image_path),
image_caption=I18N_CATALOG.i18nc("@info:status Ultimaker Cloud should not be translated.",
"Connect to Ultimaker Digital Factory"),
f"Your printer <b>{printer_name}</b> could be connected via cloud.\n Manage your print queue and monitor your prints from anywhere connecting your printer to Digital Factory"),
title = I18N_CATALOG.i18nc("@info:title", "Are you ready for cloud printing?"),
image_source = QUrl.fromLocalFile(image_path)
)
self._address = address
self.addAction("", I18N_CATALOG.i18nc("@action", "Get started"), "", "")
self._printer_name = printer_name
self.addAction("get_started", I18N_CATALOG.i18nc("@action", "Get started"), "", "")
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._onCloudFlowStarted)
def _onCloudFlowStarted(self, messageId: str, actionId: str) -> None:
QDesktopServices.openUrl(QUrl("http://{}/cloud_connect".format(self._address)))
def _onCloudFlowStarted(self, message_id: str, action_id: str) -> None:
if action_id == "get_started":
QDesktopServices.openUrl(QUrl("https://digitalfactory.ultimaker.com/app/printers?add_printer=true&utm_source=cura&utm_medium=software&utm_campaign=message-networkprinter-added"))
self.hide()
else:
QDesktopServices.openUrl(QUrl("https://support.ultimaker.com/hc/en-us/articles/360012019239?utm_source=cura&utm_medium=software&utm_campaign=add-cloud-printer"))

View File

@ -52,7 +52,6 @@ class LocalClusterOutputDeviceManager:
def start(self) -> None:
"""Start the network discovery."""
self._zero_conf_client.start()
for address in self._getStoredManualAddresses():
self.addManualDevice(address)
@ -292,4 +291,4 @@ class LocalClusterOutputDeviceManager:
if not CuraApplication.getInstance().getCuraAPI().account.isLoggedIn:
# Do not show the message if the user is not signed in.
return
CloudFlowMessage(device.ipAddress).show()
CloudFlowMessage(device.name).show()

View File

@ -133,6 +133,9 @@ class SendMaterialJob(Job):
except FileNotFoundError:
Logger.error("Unable to send material {material_id}, since it has been deleted in the meanwhile.".format(material_id = material_id))
return
except EnvironmentError as e:
Logger.error(f"Unable to send material {material_id}. We can't open that file for reading: {str(e)}")
return
# Add the material signature file if needed.
signature_file_path = "{}.sig".format(file_path)

View File

@ -52,11 +52,11 @@ class AvrFirmwareUpdater(FirmwareUpdater):
try:
programmer.programChip(hex_file)
except SerialException as e:
Logger.log("e", "A serial port exception occured during firmware update: %s" % e)
Logger.log("e", "A serial port exception occurred during firmware update: %s" % e)
self._setFirmwareUpdateState(FirmwareUpdateState.io_error)
return
except Exception as e:
Logger.log("e", "An unknown exception occured during firmware update: %s" % e)
Logger.log("e", "An unknown exception occurred during firmware update: %s" % e)
self._setFirmwareUpdateState(FirmwareUpdateState.unknown_error)
return

View File

@ -0,0 +1,137 @@
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import configparser
import io
import json
import os.path
from typing import List, Tuple
from UM.VersionUpgrade import VersionUpgrade
class VersionUpgrade411to412(VersionUpgrade):
"""
Upgrades configurations from the state they were in at version 4.11 to the
state they should be in at version 4.12.
"""
_flsun_profile_mapping = {
"extra_coarse": "flsun_sr_normal",
"coarse": "flsun_sr_normal",
"extra_fast": "flsun_sr_normal",
"draft": "flsun_sr_normal",
"fast": "flsun_sr_normal",
"normal": "flsun_sr_normal",
"high": "flsun_sr_fine"
}
_flsun_quality_type_mapping = {
"extra coarse": "normal",
"coarse" : "normal",
"verydraft" : "normal",
"draft" : "normal",
"fast" : "normal",
"normal" : "normal",
"high" : "fine"
}
def upgradePreferences(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
"""
Upgrades preferences to have the new version number.
:param serialized: The original contents of the preferences file.
:param filename: The file name of the preferences file.
:return: A list of new file names, and a list of the new contents for
those files.
"""
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialized)
# Update version number.
parser["metadata"]["setting_version"] = "19"
# If the account scope in 4.11 is outdated, delete it so that the user is enforced to log in again and get the
# correct permissions.
new_scopes = {"account.user.read",
"drive.backup.read",
"drive.backup.write",
"packages.download",
"packages.rating.read",
"packages.rating.write",
"connect.cluster.read",
"connect.cluster.write",
"library.project.read",
"library.project.write",
"cura.printjob.read",
"cura.printjob.write",
"cura.mesh.read",
"cura.mesh.write",
"cura.material.write"}
if "ultimaker_auth_data" in parser["general"]:
ultimaker_auth_data = json.loads(parser["general"]["ultimaker_auth_data"])
if new_scopes - set(ultimaker_auth_data["scope"].split(" ")):
parser["general"]["ultimaker_auth_data"] = "{}"
result = io.StringIO()
parser.write(result)
return [filename], [result.getvalue()]
def upgradeInstanceContainer(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
"""
Upgrades instance containers to have the new version number.
:param serialized: The original contents of the instance container.
:param filename: The file name of the instance container.
:return: A list of file names, and a list of the new contents for those
files.
"""
parser = configparser.ConfigParser(interpolation = None, comment_prefixes = ())
parser.read_string(serialized)
# Update setting version number.
if "metadata" not in parser:
parser["metadata"] = {}
parser["metadata"]["setting_version"] = "19"
# Update user-made quality profiles of flsun_sr printers to use the flsun_sr-specific qualities instead of the
# global ones as their base
file_base_name = os.path.basename(filename) # Remove any path-related characters from the filename
if file_base_name.startswith("flsun_sr_") and parser["metadata"].get("type") == "quality_changes":
if "general" in parser and parser["general"].get("definition") == "fdmprinter":
old_quality_type = parser["metadata"].get("quality_type", "normal")
parser["general"]["definition"] = "flsun_sr"
parser["metadata"]["quality_type"] = self._flsun_quality_type_mapping.get(old_quality_type, "normal")
result = io.StringIO()
parser.write(result)
return [filename], [result.getvalue()]
def upgradeStack(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
"""
Upgrades container stacks to have the new version number.
Upgrades container stacks for FLSun Racer to change their profiles.
:param serialized: The original contents of the container stack.
:param filename: The file name of the container stack.
:return: A list of file names, and a list of the new contents for those
files.
"""
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialized)
# Update setting version number.
if "metadata" not in parser:
parser["metadata"] = {}
parser["metadata"]["setting_version"] = "19"
# Change renamed profiles.
if "containers" in parser:
definition_id = parser["containers"].get("7")
if definition_id == "flsun_sr":
if parser["metadata"].get("type", "machine") == "machine": # Only global stacks.
old_quality = parser["containers"].get("3")
new_quality = self._flsun_profile_mapping.get(old_quality, "flsun_sr_normal")
parser["containers"]["3"] = new_quality
result = io.StringIO()
parser.write(result)
return [filename], [result.getvalue()]

View File

@ -0,0 +1,56 @@
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Any, Dict, TYPE_CHECKING
from . import VersionUpgrade411to412
if TYPE_CHECKING:
from UM.Application import Application
upgrade = VersionUpgrade411to412.VersionUpgrade411to412()
def getMetaData() -> Dict[str, Any]:
return {
"version_upgrade": {
# From To Upgrade function
("machine_stack", 5000017): ("machine_stack", 5000019, upgrade.upgradeStack),
("extruder_train", 5000017): ("extruder_train", 5000019, upgrade.upgradeStack),
("definition_changes", 4000017): ("definition_changes", 4000019, upgrade.upgradeInstanceContainer),
("quality_changes", 4000017): ("quality_changes", 4000019, upgrade.upgradeInstanceContainer),
("quality", 4000017): ("quality", 4000019, upgrade.upgradeInstanceContainer),
("user", 4000017): ("user", 4000019, upgrade.upgradeInstanceContainer),
("preferences", 7000017): ("preferences", 7000019, upgrade.upgradePreferences),
},
"sources": {
"machine_stack": {
"get_version": upgrade.getCfgVersion,
"location": {"./machine_instances"}
},
"extruder_train": {
"get_version": upgrade.getCfgVersion,
"location": {"./extruders"}
},
"definition_changes": {
"get_version": upgrade.getCfgVersion,
"location": {"./definition_changes"}
},
"quality_changes": {
"get_version": upgrade.getCfgVersion,
"location": {"./quality_changes"}
},
"quality": {
"get_version": upgrade.getCfgVersion,
"location": {"./quality"}
},
"user": {
"get_version": upgrade.getCfgVersion,
"location": {"./user"}
}
}
}
def register(app: "Application") -> Dict[str, Any]:
return {"version_upgrade": upgrade}

View File

@ -0,0 +1,8 @@
{
"name": "Version Upgrade 4.11 to 4.12",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Upgrades configurations from Cura 4.11 to Cura 4.12.",
"api": 7,
"i18n-catalog": "cura"
}

View File

@ -122,7 +122,7 @@ class VersionUpgrade42to43(VersionUpgrade):
# Update version number.
parser["metadata"]["setting_version"] = "9"
# Handle changes for the imade3d jellybox. The machine was split up into parts (eg; a 2 fan version and a single
# fan version. Perviously it used variants for this. The only upgrade we can do here is strip that variant.
# fan version. Previously it used variants for this. The only upgrade we can do here is strip that variant.
# This is because we only upgrade per stack (and to fully do these changes, we'd need to switch out something
# in the global container based on changes made to the extruder stack)
if parser["containers"]["6"] == "imade3d_jellybox_extruder_0":

View File

@ -250,7 +250,7 @@ class X3DReader(MeshReader):
else:
nr = ns = DEFAULT_SUBDIV
lau = pi / nr # Unit angle of latitude (rings) for the given tesselation
lau = pi / nr # Unit angle of latitude (rings) for the given tessellation
lou = 2 * pi / ns # Unit angle of longitude (segments)
self.reserveFaceAndVertexCount(ns*(nr*2 - 2), 2 + (nr - 1)*ns)

View File

@ -1,36 +1,38 @@
cffi==1.14.1
colorlog
cryptography==3.4.6
importlib-metadata==3.7.2
mypy==0.740
numpy==1.20.2
PyQt5==5.15.2
PyQt5-sip==12.8.1
scipy==1.6.1
shapely[vectorized]==1.7.1
twisted==21.2.0
typing
appdirs==1.4.3
certifi==2019.11.28
cffi==1.14.1
chardet==3.0.4
colorlog
comtypes==1.1.7
cryptography==3.4.8
decorator==4.4.0
idna==2.8
importlib-metadata==3.7.2
keyring==23.0.1
lxml==4.6.3
mypy==0.740
netifaces==0.10.9
networkx==2.3
networkx==2.6.2
numpy==1.20.2
numpy-stl==2.10.1
packaging==18.0
pycollada==0.6
pycparser==2.19
pycparser==2.20
pyparsing==2.4.2
PyQt5==5.15.2
PyQt5-sip==12.8.1
pyserial==3.4
pytest
python-dateutil==2.8.0
python-utils==2.3.0
pywin32==301
requests==2.22.0
scipy==1.6.2
sentry-sdk==0.13.5
shapely[vectorized]==1.7.1
six==1.12.0
trimesh==3.2.33
zeroconf==0.24.1
comtypes==1.1.7
pywin32==300
keyring==23.0.1
twisted==21.2.0
typing
urllib3==1.25.9
zeroconf==0.31.0

View File

@ -3,7 +3,7 @@ any resources here, they will not be seen or upgraded by next versions of Cura.
If you want your (unbundled or altered) resources to work after an upgrade,
please don't put them here, but install them either;
- for materials: via the Marketplace (recommended if avialable),
- for materials: via the Marketplace (recommended if available),
- by dragging a `.curapackage` file onto Cura,
- or place it in the right sub-folder in the configuration folder* for Cura.

View File

@ -6,7 +6,7 @@
"display_name": "3MF Reader",
"description": "Provides support for reading 3MF files.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -23,7 +23,7 @@
"display_name": "3MF Writer",
"description": "Provides support for writing 3MF files.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -40,7 +40,7 @@
"display_name": "AMF Reader",
"description": "Provides support for reading AMF files.",
"package_version": "1.0.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "fieldOfView",
@ -57,7 +57,7 @@
"display_name": "Cura Backups",
"description": "Backup and restore your configuration.",
"package_version": "1.2.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -74,7 +74,7 @@
"display_name": "CuraEngine Backend",
"description": "Provides the link to the CuraEngine slicing backend.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -91,7 +91,7 @@
"display_name": "Cura Profile Reader",
"description": "Provides support for importing Cura profiles.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -108,7 +108,7 @@
"display_name": "Cura Profile Writer",
"description": "Provides support for exporting Cura profiles.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -124,8 +124,8 @@
"package_type": "plugin",
"display_name": "Ultimaker Digital Library",
"description": "Connects to the Digital Library, allowing Cura to open files from and save files to the Digital Library.",
"package_version": "1.0.0",
"sdk_version": "7.7.0",
"package_version": "1.1.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -142,7 +142,7 @@
"display_name": "Firmware Update Checker",
"description": "Checks for firmware updates.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -159,7 +159,7 @@
"display_name": "Firmware Updater",
"description": "Provides a machine actions for updating firmware.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -176,7 +176,7 @@
"display_name": "Compressed G-code Reader",
"description": "Reads g-code from a compressed archive.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -193,7 +193,7 @@
"display_name": "Compressed G-code Writer",
"description": "Writes g-code to a compressed archive.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -210,7 +210,7 @@
"display_name": "G-Code Profile Reader",
"description": "Provides support for importing profiles from g-code files.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -227,7 +227,7 @@
"display_name": "G-Code Reader",
"description": "Allows loading and displaying G-code files.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "VictorLarchenko",
@ -244,7 +244,7 @@
"display_name": "G-Code Writer",
"description": "Writes g-code to a file.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -261,7 +261,7 @@
"display_name": "Image Reader",
"description": "Enables ability to generate printable geometry from 2D image files.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -278,7 +278,7 @@
"display_name": "Legacy Cura Profile Reader",
"description": "Provides support for importing profiles from legacy Cura versions.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -295,7 +295,7 @@
"display_name": "Machine Settings Action",
"description": "Provides a way to change machine settings (such as build volume, nozzle size, etc.).",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "fieldOfView",
@ -312,7 +312,7 @@
"display_name": "Model Checker",
"description": "Checks models and print configuration for possible printing issues and give suggestions.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -329,7 +329,7 @@
"display_name": "Monitor Stage",
"description": "Provides a monitor stage in Cura.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -346,7 +346,7 @@
"display_name": "Per-Object Settings Tool",
"description": "Provides the per-model settings.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -363,7 +363,7 @@
"display_name": "Post Processing",
"description": "Extension that allows for user created scripts for post processing.",
"package_version": "2.2.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -380,7 +380,7 @@
"display_name": "Prepare Stage",
"description": "Provides a prepare stage in Cura.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -397,7 +397,7 @@
"display_name": "Preview Stage",
"description": "Provides a preview stage in Cura.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -414,7 +414,7 @@
"display_name": "Removable Drive Output Device",
"description": "Provides removable drive hotplugging and writing support.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -431,7 +431,7 @@
"display_name": "Sentry Logger",
"description": "Logs certain events so that they can be used by the crash reporter",
"package_version": "1.0.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -448,7 +448,7 @@
"display_name": "Simulation View",
"description": "Provides the Simulation view.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -465,7 +465,7 @@
"display_name": "Slice Info",
"description": "Submits anonymous slice info. Can be disabled through preferences.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -482,7 +482,7 @@
"display_name": "Solid View",
"description": "Provides a normal solid mesh view.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -499,7 +499,7 @@
"display_name": "Support Eraser Tool",
"description": "Creates an eraser mesh to block the printing of support in certain places.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -516,7 +516,7 @@
"display_name": "Trimesh Reader",
"description": "Provides support for reading model files.",
"package_version": "1.0.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -533,7 +533,7 @@
"display_name": "Toolbox",
"description": "Find, manage and install new Cura packages.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -550,7 +550,7 @@
"display_name": "UFP Reader",
"description": "Provides support for reading Ultimaker Format Packages.",
"package_version": "1.0.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -567,7 +567,7 @@
"display_name": "UFP Writer",
"description": "Provides support for writing Ultimaker Format Packages.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -584,7 +584,7 @@
"display_name": "Ultimaker Machine Actions",
"description": "Provides machine actions for Ultimaker machines (such as bed leveling wizard, selecting upgrades, etc.).",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -601,7 +601,7 @@
"display_name": "UM3 Network Printing",
"description": "Manages network connections to Ultimaker 3 printers.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -618,7 +618,7 @@
"display_name": "USB Printing",
"description": "Accepts G-Code and sends them to a printer. Plugin can also update firmware.",
"package_version": "1.0.2",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -635,7 +635,7 @@
"display_name": "Version Upgrade 2.1 to 2.2",
"description": "Upgrades configurations from Cura 2.1 to Cura 2.2.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -652,7 +652,7 @@
"display_name": "Version Upgrade 2.2 to 2.4",
"description": "Upgrades configurations from Cura 2.2 to Cura 2.4.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -669,7 +669,7 @@
"display_name": "Version Upgrade 2.5 to 2.6",
"description": "Upgrades configurations from Cura 2.5 to Cura 2.6.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -686,7 +686,7 @@
"display_name": "Version Upgrade 2.6 to 2.7",
"description": "Upgrades configurations from Cura 2.6 to Cura 2.7.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -703,7 +703,7 @@
"display_name": "Version Upgrade 2.7 to 3.0",
"description": "Upgrades configurations from Cura 2.7 to Cura 3.0.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -720,7 +720,7 @@
"display_name": "Version Upgrade 3.0 to 3.1",
"description": "Upgrades configurations from Cura 3.0 to Cura 3.1.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -737,7 +737,7 @@
"display_name": "Version Upgrade 3.2 to 3.3",
"description": "Upgrades configurations from Cura 3.2 to Cura 3.3.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -754,7 +754,7 @@
"display_name": "Version Upgrade 3.3 to 3.4",
"description": "Upgrades configurations from Cura 3.3 to Cura 3.4.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -771,7 +771,7 @@
"display_name": "Version Upgrade 3.4 to 3.5",
"description": "Upgrades configurations from Cura 3.4 to Cura 3.5.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -788,7 +788,7 @@
"display_name": "Version Upgrade 3.5 to 4.0",
"description": "Upgrades configurations from Cura 3.5 to Cura 4.0.",
"package_version": "1.0.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -805,7 +805,7 @@
"display_name": "Version Upgrade 4.0 to 4.1",
"description": "Upgrades configurations from Cura 4.0 to Cura 4.1.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -822,7 +822,7 @@
"display_name": "Version Upgrade 4.1 to 4.2",
"description": "Upgrades configurations from Cura 4.1 to Cura 4.2.",
"package_version": "1.0.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -839,7 +839,7 @@
"display_name": "Version Upgrade 4.2 to 4.3",
"description": "Upgrades configurations from Cura 4.2 to Cura 4.3.",
"package_version": "1.0.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -856,7 +856,7 @@
"display_name": "Version Upgrade 4.3 to 4.4",
"description": "Upgrades configurations from Cura 4.3 to Cura 4.4.",
"package_version": "1.0.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -873,7 +873,7 @@
"display_name": "Version Upgrade 4.4 to 4.5",
"description": "Upgrades configurations from Cura 4.4 to Cura 4.5.",
"package_version": "1.0.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -890,7 +890,7 @@
"display_name": "Version Upgrade 4.5 to 4.6",
"description": "Upgrades configurations from Cura 4.5 to Cura 4.6.",
"package_version": "1.0.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -907,7 +907,7 @@
"display_name": "Version Upgrade 4.6.0 to 4.6.2",
"description": "Upgrades configurations from Cura 4.6.0 to Cura 4.6.2.",
"package_version": "1.0.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -924,7 +924,7 @@
"display_name": "Version Upgrade 4.6.2 to 4.7",
"description": "Upgrades configurations from Cura 4.6.2 to Cura 4.7.",
"package_version": "1.0.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -941,7 +941,7 @@
"display_name": "Version Upgrade 4.7.0 to 4.8.0",
"description": "Upgrades configurations from Cura 4.7.0 to Cura 4.8.0",
"package_version": "1.0.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -958,7 +958,7 @@
"display_name": "Version Upgrade 4.8.0 to 4.9.0",
"description": "Upgrades configurations from Cura 4.8.0 to Cura 4.9.0",
"package_version": "1.0.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -976,6 +976,24 @@
"description": "Upgrades configurations from Cura 4.9 to Cura 4.10",
"package_version": "1.0.0",
"sdk_version": 7,
"sdk_version_semver": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
}
},
"VersionUpgrade411to412": {
"package_info": {
"package_id": "VersionUpgrade411to412",
"package_type": "plugin",
"display_name": "Version Upgrade 4.11 to 4.12",
"description": "Upgrades configurations from Cura 4.11 to Cura 4.12",
"package_version": "1.0.0",
"sdk_version": 7,
"sdk_version_semver": "7.7.0",
"website": "https://ultimaker.com",
"author": {
@ -993,7 +1011,7 @@
"display_name": "X3D Reader",
"description": "Provides support for reading X3D files.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "SevaAlekseyev",
@ -1010,7 +1028,7 @@
"display_name": "XML Material Profiles",
"description": "Provides capabilities to read and write XML-based material profiles.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -1027,7 +1045,7 @@
"display_name": "X-Ray View",
"description": "Provides the X-Ray view.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
@ -1044,7 +1062,7 @@
"display_name": "Generic ABS",
"description": "The generic ABS profile which other profiles can be based upon.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@ -1062,7 +1080,7 @@
"display_name": "Generic BAM",
"description": "The generic BAM profile which other profiles can be based upon.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@ -1080,7 +1098,7 @@
"display_name": "Generic CFF CPE",
"description": "The generic CFF CPE profile which other profiles can be based upon.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@ -1098,7 +1116,7 @@
"display_name": "Generic CFF PA",
"description": "The generic CFF PA profile which other profiles can be based upon.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@ -1116,7 +1134,7 @@
"display_name": "Generic CPE",
"description": "The generic CPE profile which other profiles can be based upon.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@ -1134,7 +1152,7 @@
"display_name": "Generic CPE+",
"description": "The generic CPE+ profile which other profiles can be based upon.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@ -1152,7 +1170,7 @@
"display_name": "Generic GFF CPE",
"description": "The generic GFF CPE profile which other profiles can be based upon.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@ -1170,7 +1188,7 @@
"display_name": "Generic GFF PA",
"description": "The generic GFF PA profile which other profiles can be based upon.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@ -1188,7 +1206,7 @@
"display_name": "Generic HIPS",
"description": "The generic HIPS profile which other profiles can be based upon.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@ -1206,7 +1224,7 @@
"display_name": "Generic Nylon",
"description": "The generic Nylon profile which other profiles can be based upon.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@ -1224,7 +1242,7 @@
"display_name": "Generic PC",
"description": "The generic PC profile which other profiles can be based upon.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@ -1242,7 +1260,7 @@
"display_name": "Generic PETG",
"description": "The generic PETG profile which other profiles can be based upon.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@ -1260,7 +1278,7 @@
"display_name": "Generic PLA",
"description": "The generic PLA profile which other profiles can be based upon.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@ -1278,7 +1296,7 @@
"display_name": "Generic PP",
"description": "The generic PP profile which other profiles can be based upon.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@ -1296,7 +1314,7 @@
"display_name": "Generic PVA",
"description": "The generic PVA profile which other profiles can be based upon.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@ -1314,7 +1332,7 @@
"display_name": "Generic Tough PLA",
"description": "The generic Tough PLA profile which other profiles can be based upon.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@ -1332,7 +1350,7 @@
"display_name": "Generic TPU",
"description": "The generic TPU profile which other profiles can be based upon.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@ -1350,7 +1368,7 @@
"display_name": "Dagoma Chromatik PLA",
"description": "Filament testé et approuvé pour les imprimantes 3D Dagoma. Chromatik est l'idéal pour débuter et suivre les tutoriels premiers pas. Il vous offre qualité et résistance pour chacune de vos impressions.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://dagoma.fr/boutique/filaments.html",
"author": {
"author_id": "Dagoma",
@ -1367,7 +1385,7 @@
"display_name": "FABtotum ABS",
"description": "This material is easy to be extruded but it is not the simplest to use. It is one of the most used in 3D printing to get very well finished objects. It is not sustainable and its smoke can be dangerous if inhaled. The reason to prefer this filament to PLA is mainly because of its precision and mechanical specs. ABS (for plastic) stands for Acrylonitrile Butadiene Styrene and it is a thermoplastic which is widely used in everyday objects. It can be printed with any FFF 3D printer which can get to high temperatures as it must be extruded in a range between 220° and 245°, so its compatible with all versions of the FABtotum Personal fabricator.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=40",
"author": {
"author_id": "FABtotum",
@ -1384,7 +1402,7 @@
"display_name": "FABtotum Nylon",
"description": "When 3D printing started this material was not listed among the extrudable filaments. It is flexible as well as resistant to tractions. It is well known for its uses in textile but also in industries which require a strong and flexible material. There are different kinds of Nylon: 3D printing mostly uses Nylon 6 and Nylon 6.6, which are the most common. It requires higher temperatures to be printed, so a 3D printer must be able to reach them (around 240°C): the FABtotum, of course, can.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=53",
"author": {
"author_id": "FABtotum",
@ -1401,7 +1419,7 @@
"display_name": "FABtotum PLA",
"description": "It is the most common filament used for 3D printing. It is studied to be bio-degradable as it comes from corn starchs sugar mainly. It is completely made of renewable sources and has no footprint on polluting. PLA stands for PolyLactic Acid and it is a thermoplastic that today is still considered the easiest material to be 3D printed. It can be extruded at lower temperatures: the standard range of FABtotums one is between 185° and 195°.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=39",
"author": {
"author_id": "FABtotum",
@ -1418,7 +1436,7 @@
"display_name": "FABtotum TPU Shore 98A",
"description": "",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=66",
"author": {
"author_id": "FABtotum",
@ -1435,7 +1453,7 @@
"display_name": "Fiberlogy HD PLA",
"description": "With our HD PLA you have many more options. You can use this material in two ways. Choose the one you like best. You can use it as a normal PLA and get prints characterized by a very good adhesion between the layers and high precision. You can also make your prints acquire similar properties to that of ABS better impact resistance and high temperature resistance. All you need is an oven. Yes, an oven! By annealing our HD PLA in an oven, in accordance with the manual, you will avoid all the inconveniences of printing with ABS, such as unpleasant odour or hazardous fumes.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "http://fiberlogy.com/en/fiberlogy-filaments/filament-hd-pla/",
"author": {
"author_id": "Fiberlogy",
@ -1452,7 +1470,7 @@
"display_name": "Filo3D PLA",
"description": "Fast, safe and reliable printing. PLA is ideal for the fast and reliable printing of parts and prototypes with a great surface quality.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://dagoma.fr",
"author": {
"author_id": "Dagoma",
@ -1469,7 +1487,7 @@
"display_name": "IMADE3D JellyBOX PETG",
"description": "",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "http://shop.imade3d.com/filament.html",
"author": {
"author_id": "IMADE3D",
@ -1486,7 +1504,7 @@
"display_name": "IMADE3D JellyBOX PLA",
"description": "",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "http://shop.imade3d.com/filament.html",
"author": {
"author_id": "IMADE3D",
@ -1503,7 +1521,7 @@
"display_name": "Octofiber PLA",
"description": "PLA material from Octofiber.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://nl.octofiber.com/3d-printing-filament/pla.html",
"author": {
"author_id": "Octofiber",
@ -1520,7 +1538,7 @@
"display_name": "PolyFlex™ PLA",
"description": "PolyFlex™ is a highly flexible yet easy to print 3D printing material. Featuring good elasticity and a large strain-to- failure, PolyFlex™ opens up a completely new realm of applications.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "http://www.polymaker.com/shop/polyflex/",
"author": {
"author_id": "Polymaker",
@ -1537,7 +1555,7 @@
"display_name": "PolyMax™ PLA",
"description": "PolyMax™ PLA is a 3D printing material with excellent mechanical properties and printing quality. PolyMax™ PLA has an impact resistance of up to nine times that of regular PLA, and better overall mechanical properties than ABS.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "http://www.polymaker.com/shop/polymax/",
"author": {
"author_id": "Polymaker",
@ -1554,7 +1572,7 @@
"display_name": "PolyPlus™ PLA True Colour",
"description": "PolyPlus™ PLA is a premium PLA designed for all desktop FDM/FFF 3D printers. It is produced with our patented Jam-Free™ technology that ensures consistent extrusion and prevents jams.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "http://www.polymaker.com/shop/polyplus-true-colour/",
"author": {
"author_id": "Polymaker",
@ -1571,7 +1589,7 @@
"display_name": "PolyWood™ PLA",
"description": "PolyWood™ is a wood mimic printing material that contains no actual wood ensuring a clean Jam-Free™ printing experience.",
"package_version": "1.0.1",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "http://www.polymaker.com/shop/polywood/",
"author": {
"author_id": "Polymaker",
@ -1588,7 +1606,7 @@
"display_name": "Ultimaker ABS",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com/products/materials/abs",
"author": {
"author_id": "UltimakerPackages",
@ -1607,7 +1625,7 @@
"display_name": "Ultimaker Breakaway",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com/products/materials/breakaway",
"author": {
"author_id": "UltimakerPackages",
@ -1626,7 +1644,7 @@
"display_name": "Ultimaker CPE",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com/products/materials/abs",
"author": {
"author_id": "UltimakerPackages",
@ -1645,7 +1663,7 @@
"display_name": "Ultimaker CPE+",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com/products/materials/cpe",
"author": {
"author_id": "UltimakerPackages",
@ -1664,7 +1682,7 @@
"display_name": "Ultimaker Nylon",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com/products/materials/abs",
"author": {
"author_id": "UltimakerPackages",
@ -1683,7 +1701,7 @@
"display_name": "Ultimaker PC",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com/products/materials/pc",
"author": {
"author_id": "UltimakerPackages",
@ -1702,7 +1720,7 @@
"display_name": "Ultimaker PLA",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com/products/materials/abs",
"author": {
"author_id": "UltimakerPackages",
@ -1721,7 +1739,7 @@
"display_name": "Ultimaker PP",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com/products/materials/pp",
"author": {
"author_id": "UltimakerPackages",
@ -1740,7 +1758,7 @@
"display_name": "Ultimaker PVA",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com/products/materials/abs",
"author": {
"author_id": "UltimakerPackages",
@ -1759,7 +1777,7 @@
"display_name": "Ultimaker TPU 95A",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com/products/materials/tpu-95a",
"author": {
"author_id": "UltimakerPackages",
@ -1778,7 +1796,7 @@
"display_name": "Ultimaker Tough PLA",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://ultimaker.com/products/materials/tough-pla",
"author": {
"author_id": "UltimakerPackages",
@ -1797,7 +1815,7 @@
"display_name": "Vertex Delta ABS",
"description": "ABS material and quality files for the Delta Vertex K8800.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://vertex3dprinter.eu",
"author": {
"author_id": "Velleman",
@ -1814,7 +1832,7 @@
"display_name": "Vertex Delta PET",
"description": "ABS material and quality files for the Delta Vertex K8800.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://vertex3dprinter.eu",
"author": {
"author_id": "Velleman",
@ -1831,7 +1849,7 @@
"display_name": "Vertex Delta PLA",
"description": "ABS material and quality files for the Delta Vertex K8800.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://vertex3dprinter.eu",
"author": {
"author_id": "Velleman",
@ -1848,7 +1866,7 @@
"display_name": "Vertex Delta TPU",
"description": "ABS material and quality files for the Delta Vertex K8800.",
"package_version": "1.4.0",
"sdk_version": "7.7.0",
"sdk_version": "7.8.0",
"website": "https://vertex3dprinter.eu",
"author": {
"author_id": "Velleman",

View File

@ -0,0 +1,49 @@
{
"version": 2,
"name": "3DI Base Printer",
"inherits": "fdmprinter",
"metadata": {
"visible": false,
"author": "Vaibhav Jain",
"manufacturer": "3Deometry Innovations",
"file_formats": "text/x-gcode",
"machine_extruder_trains":
{
"0": "3di_base_extruder_0"
}
},
"overrides": {
"machine_name":{
"default_value": "3DI Base Printer"
},
"machine_heated_bed": {
"default_value": true
},
"machine_width": {
"default_value": 220
},
"machine_height": {
"default_value": 220
},
"machine_depth": {
"default_value": 220
},
"machine_center_is_zero": {
"default_value": true
},
"machine_gcode_flavor": {
"default_value": "RepRap (Marlin/Sprinter)"
},
"machine_start_gcode": {
"default_value": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 ;Home all axes (max endstops)\nG1 Z15.0 F9000 ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F9000\n;Put printing message on LCD screen\nM117 Printing..."
},
"machine_end_gcode": {
"default_value": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG28 ;Home all axes (max endstops)\nM84 ;steppers off\nG90 ;absolute positioning"
},
"machine_shape": {
"default_value": "elliptic"
}
}
}

View File

@ -0,0 +1,26 @@
{
"version": 2,
"name": "3DI D300",
"inherits": "3di_base",
"metadata": {
"visible": true,
"platform": "3di_d300_platform.STL",
"platform_offset": [-200, -5, 173.205]
},
"overrides": {
"machine_name": {
"default_value": "3DI D300"
},
"machine_width": {
"default_value": 300
},
"machine_height": {
"default_value": 300
},
"machine_depth": {
"default_value": 300
}
}
}

View File

@ -92,7 +92,7 @@
"retraction_hop_enabled": { "value": "True" },
"retraction_hop": { "value": 0.2 },
"retraction_combing": { "default_value": "noskin" },
"retraction_combing": { "value": "'noskin'" },
"retraction_combing_max_distance": { "value": 30 },
"travel_avoid_other_parts": { "value": true },

View File

@ -0,0 +1,52 @@
{
"version": 2,
"name": "Arjun Pro 300",
"inherits": "fdmprinter",
"metadata": {
"visible": true,
"author": "Venkat Kamesh",
"manufacturer": "Sri Vignan Technologies",
"weight": 3,
"file_formats": "text/x-gcode",
"platform": "arjunpro300_platform.STL",
"platform_offset": [-155, -6, 190],
"has_material": true,
"has_variants": true,
"preferred_variant_name": "0.4 mm Nozzle",
"machine_extruder_trains":
{
"0": "arjunpro_extruder_0",
"1": "arjunpro_extruder_1"
}
},
"overrides": {
"machine_name": { "default_value": "Arjun Pro 300" },
"machine_width": { "default_value": 300 },
"machine_height": { "default_value": 293 },
"machine_depth": { "default_value": 300 },
"machine_center_is_zero": {"default_value": false},
"machine_heated_bed": { "default_value": true },
"machine_nozzle_size": {"default_value": 0.4},
"machine_show_variants": {"default_value": true},
"machine_acceleration": {"default_value": 2000},
"machine_max_feedrate_x": { "value": 300 },
"machine_max_feedrate_y": { "value": 300 },
"machine_max_feedrate_z": { "value": 15 },
"machine_max_feedrate_e": { "value": 150 },
"machine_use_extruder_offset_to_offset_coords": {"default_value": false},
"line_width": {"value": "machine_nozzle_size"},
"speed_travel": {"maximum_value": "300", "value": "200"},
"optimize_wall_printing_order": { "value": "True" },
"material_diameter": { "default_value": 1.75},
"retraction_amount": {"default_value": 6.5},
"retraction_speed": { "default_value": 30},
"adhesion_type": { "default_value": "skirt" },
"machine_gcode_flavor": { "default_value": "Marlin"},
"ironing_enabled":{"default_value": true},
"machine_start_gcode": { "default_value": "M605 S0\nG21\nG90\nM82\nM107\nT1\nG28 \nG29 \nG1 X0 Y5 F2000\nT1\nG92 E0\nG1 E45 F210\nG92 E0\nT0\nG92 E0\nG1 E45 F210\nG92 E0\nM117\n"},
"machine_end_gcode": { "default_value": "G91\nG1 Z+0.5 E-16 Y+10 F9000\nG90\nM107\nM104 S0 T1\nM104 S0 T0\nM140 S0\nM117\nG28 X0 Y0\nT0\nM84"},
"machine_extruder_count": { "default_value": 2 }
}
}

View File

@ -0,0 +1,49 @@
{
"version": 2,
"name": "Arjun Pro 300 Duplication",
"inherits": "fdmprinter",
"metadata": {
"visible": true,
"author": "Venkat Kamesh",
"manufacturer": "Sri Vignan Technologies",
"weight": 3,
"file_formats": "text/x-gcode",
"has_material": true,
"has_variants": true,
"preferred_variant_name": "0.4 mm Nozzle",
"machine_extruder_trains":
{
"0": "arjunpro_dm_extruder"
}
},
"overrides": {
"machine_name": { "default_value": "Arjunpro 300 Duplication" },
"machine_width": { "default_value": 120 },
"machine_height": { "default_value": 293 },
"machine_depth": { "default_value": 300 },
"machine_center_is_zero": {"default_value": false},
"machine_heated_bed": { "default_value": true },
"machine_nozzle_size": {"default_value": 0.4},
"machine_show_variants": {"default_value": true},
"machine_acceleration": {"default_value": 2000},
"machine_max_feedrate_x": { "value": 300 },
"machine_max_feedrate_y": { "value": 300 },
"machine_max_feedrate_z": { "value": 15 },
"machine_max_feedrate_e": { "value": 150 },
"machine_use_extruder_offset_to_offset_coords": {"default_value": false},
"line_width": {"value": "machine_nozzle_size"},
"speed_travel": {"maximum_value": "300", "value": "200"},
"optimize_wall_printing_order": { "value": "True" },
"material_diameter": { "default_value": 1.75},
"retraction_amount": {"default_value": 6.5},
"retraction_speed": { "default_value": 30},
"adhesion_type": { "default_value": "skirt" },
"machine_gcode_flavor": { "default_value": "Marlin"},
"ironing_enabled":{"default_value": true},
"machine_start_gcode": {"default_value": "M605 S2 R0 X125\nG21\nG90\nM82\nM107\nM104 S{material_print_temperature}\nM105\nM109 S{material_print_temperature}\nG28 \nG29 \nG1 Z15 F150\nG28 Y5\nG1 Y20 F6000\nG28 X0\nG1 X80 F6000\nT0\nG92 E0\nG1 E35 F250\nG1 E45 F120\nG92 E0\nG1 X100 Z0 F5000\nG1 X125 F6000\nM117\n"},
"machine_end_gcode": {"default_value": "G91\nG1 Z+0.5 E-16 Y+10 F9000\nG90\nM107\nM107 P1\nM104 S0\nM140 S0\nM117\nM605 S0\nG28 X0 Y0\nM84"},
"machine_extruder_count": { "default_value": 1 }
}
}

View File

@ -0,0 +1,49 @@
{
"version": 2,
"name": "Arjun Pro 300 Mirror",
"inherits": "fdmprinter",
"metadata": {
"visible": true,
"author": "Venkat Kamesh",
"manufacturer": "Sri Vignan Technologies",
"weight": 3,
"file_formats": "text/x-gcode",
"has_material": true,
"has_variants": true,
"preferred_variant_name": "0.4 mm Nozzle",
"machine_extruder_trains":
{
"0": "arjunpro_mm_extruder"
}
},
"overrides": {
"machine_name": { "default_value": "Arjunpro 300 Mirror" },
"machine_width": { "default_value": 120 },
"machine_height": { "default_value": 293 },
"machine_depth": { "default_value": 300 },
"machine_center_is_zero": {"default_value": false},
"machine_heated_bed": { "default_value": true },
"machine_nozzle_size": {"default_value": 0.4},
"machine_show_variants": {"default_value": true},
"machine_acceleration": {"default_value": 2000},
"machine_max_feedrate_x": { "value": 300 },
"machine_max_feedrate_y": { "value": 300 },
"machine_max_feedrate_z": { "value": 15 },
"machine_max_feedrate_e": { "value": 150 },
"machine_use_extruder_offset_to_offset_coords": {"default_value": false},
"line_width": {"value": "machine_nozzle_size"},
"speed_travel": {"maximum_value": "300", "value": "200"},
"optimize_wall_printing_order": { "value": "True" },
"material_diameter": { "default_value": 1.75},
"retraction_amount": {"default_value": 6.5},
"retraction_speed": { "default_value": 30},
"adhesion_type": { "default_value": "skirt" },
"machine_gcode_flavor": { "default_value": "Marlin"},
"ironing_enabled":{"default_value": true},
"machine_start_gcode": {"default_value": "M605 S2 R0 X125\nM605 S3 X125\nG21\nG90\nM82\nM107\nM104 S{material_print_temperature}\nM105\nM109 S{material_print_temperature}\nG28 \nG29 \nG1 Z15 F150\nG28 Y5\nG1 Y20 F6000\nG28 X0\nG1 X80 F6000\nT0\nG92 E0\nG1 E35 F250\nG1 E45 F120\nG92 E0\nG1 X100 Z0 F5000\nG1 X125 F6000\nM117\n"},
"machine_end_gcode": {"default_value": "G91\nG1 Z+0.5 E-16 Y+10 F9000\nG90\nM107\nM107 P1\nM104 S0\nM140 S0\nM117\nM605 S0\nG28 X0 Y0\nM84"},
"machine_extruder_count": { "default_value": 1 }
}
}

View File

@ -225,7 +225,7 @@
"retraction_prime_speed": { "value": "math.ceil(retraction_speed * 0.4)", "maximum_value_warning": "130" },
"retraction_hop_enabled": { "value": "True" },
"retraction_hop": { "value": "0.5" },
"retraction_combing": { "default_value": "noskin" },
"retraction_combing": { "value": "'noskin'" },
"retraction_combing_max_distance": { "value": "10" },
"travel_avoid_other_parts": { "value": "True" },
"travel_avoid_supports": { "value": "True" },

View File

@ -0,0 +1,29 @@
{
"version": 2,
"name": "AtomStack Cambrian Base Printer",
"inherits": "fdmprinter",
"metadata": {
"visible": false,
"author": "AtomStack",
"manufacturer": "AtomStack",
"file_formats": "text/x-gcode",
"has_materials": true,
"machine_extruder_trains": {
"0": "atomstack_cambrianmaxe175_extruder_0"
}
},
"overrides": {
"machine_heated_bed": {
"default_value": true
},
"machine_extruder_count": {
"default_value": 1
},
"machine_start_gcode": {
"default_value": "; AtomStack Cambrian Start G-code\nG92 E0 ; Reset Extruder\nG28 ; Home all axes\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.9 Y200.0 Z0.3 F5000.0 ; Move to side a little\nG1 X0.9 Y20 Z0.3 F1500.0 E30 ; Draw the second line\nG1 E29 ;Retract the filament\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"
},
"machine_end_gcode": {
"default_value": "; AtomStack Cambrian End G-code\nG91 ;Relative positioning\nG1 E-2 F2700 ;Retract a bit\nG1 E-8 Z0.2 F2400 ;Retract and raise Z\nG1 X5 Y5 F3000 ;Wipe out\nG1 Z10 ;Raise Z more\nG90 ;Absolute position\n\nG1 X0 Y210 ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off nozzle\nM140 S0 ;Turn-off bed\n\nM84 X Y E ;Disable all steppers but Z"
}
}
}

View File

@ -0,0 +1,29 @@
{
"version": 2,
"name": "Cambrian Max E175",
"inherits": "atomstack_cambrian_base",
"metadata": {
"visible": true,
"author": "AtomStack",
"manufacturer": "AtomStack",
"file_formats": "text/x-gcode",
"has_materials": true,
"machine_extruder_trains": {
"0": "atomstack_cambrianmaxe175_extruder_0"
}
},
"overrides": {
"machine_name": {
"default_value": "Cambrian MAX E175"
},
"machine_width": {
"default_value": 320
},
"machine_height": {
"default_value": 380
},
"machine_depth": {
"default_value": 330
}
}
}

View File

@ -0,0 +1,29 @@
{
"version": 2,
"name": "Cambrian Max E285",
"inherits": "atomstack_cambrian_base",
"metadata": {
"visible": true,
"author": "AtomStack",
"manufacturer": "AtomStack",
"file_formats": "text/x-gcode",
"has_materials": true,
"machine_extruder_trains": {
"0": "atomstack_cambrianmaxe285_extruder_0"
}
},
"overrides": {
"machine_name": {
"default_value": "Cambrian MAX E285"
},
"machine_width": {
"default_value": 320
},
"machine_height": {
"default_value": 380
},
"machine_depth": {
"default_value": 330
}
}
}

View File

@ -0,0 +1,29 @@
{
"version": 2,
"name": "Cambrian Pro E175",
"inherits": "atomstack_cambrian_base",
"metadata": {
"visible": true,
"author": "AtomStack",
"manufacturer": "AtomStack",
"file_formats": "text/x-gcode",
"has_materials": true,
"machine_extruder_trains": {
"0": "atomstack_cambrianproe175_extruder_0"
}
},
"overrides": {
"machine_name": {
"default_value": "Cambrian Pro E175"
},
"machine_width": {
"default_value": 235
},
"machine_height": {
"default_value": 250
},
"machine_depth": {
"default_value": 235
}
}
}

View File

@ -0,0 +1,29 @@
{
"version": 2,
"name": "Cambrian Pro E285",
"inherits": "atomstack_cambrian_base",
"metadata": {
"visible": true,
"author": "AtomStack",
"manufacturer": "AtomStack",
"file_formats": "text/x-gcode",
"has_materials": true,
"machine_extruder_trains": {
"0": "atomstack_cambrianproe285_extruder_0"
}
},
"overrides": {
"machine_name": {
"default_value": "Cambrian Pro E285"
},
"machine_width": {
"default_value": 235
},
"machine_height": {
"default_value": 250
},
"machine_depth": {
"default_value": 235
}
}
}

View File

@ -4,7 +4,6 @@
"inherits": "blv_mgn_cube_base",
"metadata": {
"visible": true,
"setting_version": 17,
"author": "wolfgangmauer",
"manufacturer": "BLV",
"file_formats": "text/x-gcode",

View File

@ -4,7 +4,6 @@
"inherits": "blv_mgn_cube_base",
"metadata": {
"visible": true,
"setting_version": 17,
"author": "wolfgangmauer",
"manufacturer": "BLV",
"file_formats": "text/x-gcode",

View File

@ -5,6 +5,7 @@
"overrides": {
"machine_name": { "default_value": "Creality Ender-5 Plus" },
"machine_start_gcode": { "default_value": "M201 X500.00 Y500.00 Z100.00 E5000.00 ;Setup machine max acceleration\nM203 X500.00 Y500.00 Z10.00 E50.00 ;Setup machine max feedrate\nM204 P500.00 R1000.00 T500.00 ;Setup Print/Retract/Travel acceleration\nM205 X8.00 Y8.00 Z0.40 E5.00 ;Setup Jerk\nM220 S100 ;Reset Feedrate\nM221 S100 ;Reset Flowrate\n\nG28 ;Home\nM420 S1 Z2 ;Enable ABL using saved Mesh and Fade Height\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_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\nG1 X{machine_width} Y{machine_depth} ;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_width": { "default_value": 350 },
"machine_depth": { "default_value": 350 },
"machine_height": { "default_value": 400 },

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