diff --git a/cura/API/Backups.py b/cura/API/Backups.py index 1940d38a36..a52dcbfb6b 100644 --- a/cura/API/Backups.py +++ b/cura/API/Backups.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018 Ultimaker B.V. +# Copyright (c) 2025 UltiMaker # Cura is released under the terms of the LGPLv3 or higher. from typing import Tuple, Optional, TYPE_CHECKING, Dict, Any @@ -9,14 +9,10 @@ if TYPE_CHECKING: class Backups: - """The back-ups API provides a version-proof bridge between Cura's - - BackupManager and plug-ins that hook into it. + """The back-ups API provides a version-proof bridge between Cura's BackupManager and plug-ins that hook into it. Usage: - .. code-block:: python - from cura.API import CuraAPI api = CuraAPI() api.backups.createBackup() @@ -26,19 +22,22 @@ class Backups: def __init__(self, application: "CuraApplication") -> None: self.manager = BackupsManager(application) - def createBackup(self) -> Tuple[Optional[bytes], Optional[Dict[str, Any]]]: + def createBackup(self, available_remote_plugins: frozenset[str] = frozenset()) -> Tuple[Optional[bytes], Optional[Dict[str, Any]]]: """Create a new back-up using the BackupsManager. :return: Tuple containing a ZIP file with the back-up data and a dict with metadata about the back-up. """ - return self.manager.createBackup() + return self.manager.createBackup(available_remote_plugins) - def restoreBackup(self, zip_file: bytes, meta_data: Dict[str, Any]) -> None: + def restoreBackup(self, zip_file: bytes, meta_data: Dict[str, Any], auto_close: bool = True) -> None: """Restore a back-up using the BackupsManager. :param zip_file: A ZIP file containing the actual back-up data. :param meta_data: Some metadata needed for restoring a back-up, like the Cura version number. """ - return self.manager.restoreBackup(zip_file, meta_data) + return self.manager.restoreBackup(zip_file, meta_data, auto_close=auto_close) + + def shouldReinstallDownloadablePlugins(self) -> bool: + return self.manager.shouldReinstallDownloadablePlugins() diff --git a/cura/Backups/Backup.py b/cura/Backups/Backup.py index 19655df531..1438ce293a 100644 --- a/cura/Backups/Backup.py +++ b/cura/Backups/Backup.py @@ -1,5 +1,8 @@ -# Copyright (c) 2021 Ultimaker B.V. +# Copyright (c) 2025 UltiMaker # Cura is released under the terms of the LGPLv3 or higher. +import tempfile + +import json import io import os @@ -7,12 +10,13 @@ import re import shutil from copy import deepcopy from zipfile import ZipFile, ZIP_DEFLATED, BadZipfile -from typing import Dict, Optional, TYPE_CHECKING, List +from typing import Callable, Dict, Optional, TYPE_CHECKING, List from UM import i18nCatalog from UM.Logger import Logger from UM.Message import Message from UM.Platform import Platform +from UM.PluginRegistry import PluginRegistry from UM.Resources import Resources from UM.Version import Version @@ -30,6 +34,7 @@ class Backup: """These files should be ignored when making a backup.""" IGNORED_FOLDERS = [] # type: List[str] + """These folders should be ignored when making a backup.""" SECRETS_SETTINGS = ["general/ultimaker_auth_data"] """Secret preferences that need to obfuscated when making a backup of Cura""" @@ -42,7 +47,7 @@ class Backup: self.zip_file = zip_file # type: Optional[bytes] self.meta_data = meta_data # type: Optional[Dict[str, str]] - def makeFromCurrent(self) -> None: + def makeFromCurrent(self, available_remote_plugins: frozenset[str] = frozenset()) -> None: """Create a back-up from the current user config folder.""" cura_release = self._application.getVersion() @@ -68,7 +73,7 @@ class Backup: # Create an empty buffer and write the archive to it. buffer = io.BytesIO() - archive = self._makeArchive(buffer, version_data_dir) + archive = self._makeArchive(buffer, version_data_dir, available_remote_plugins) if archive is None: return files = archive.namelist() @@ -77,9 +82,7 @@ class Backup: machine_count = max(len([s for s in files if "machine_instances/" in s]) - 1, 0) # If people delete their profiles but not their preferences, it can still make a backup, and report -1 profiles. Server crashes on this. material_count = max(len([s for s in files if "materials/" in s]) - 1, 0) profile_count = max(len([s for s in files if "quality_changes/" in s]) - 1, 0) - # We don't store plugins anymore, since if you can make backups, you have an account (and the plugins are - # on the marketplace anyway) - plugin_count = 0 + plugin_count = len([s for s in files if "plugin.json" in s]) # Store the archive and metadata so the BackupManager can fetch them when needed. self.zip_file = buffer.getvalue() self.meta_data = { @@ -92,22 +95,72 @@ class Backup: # Restore the obfuscated settings self._illuminate(**secrets) - def _makeArchive(self, buffer: "io.BytesIO", root_path: str) -> Optional[ZipFile]: + def _fillToInstallsJson(self, file_path: str, reinstall_on_restore: frozenset[str], add_to_archive: Callable[[str, str], None]) -> Optional[str]: + """ Moves all plugin-data (in a config-file) for plugins that could be (re)installed from the Marketplace from + 'installed' to 'to_installs' before adding that file to the archive. + + Note that the 'filename'-entry in the package-data (of the plugins) might not be valid anymore on restore. + We'll replace it on restore instead, as that's the time when the new package is downloaded. + + :param file_path: Absolute path to the packages-file. + :param reinstall_on_restore: A set of plugins that _can_ be reinstalled from the Marketplace. + :param add_to_archive: A function/lambda that takes a filename and adds it to the archive (as the 2nd name). + """ + with open(file_path, "r") as file: + data = json.load(file) + reinstall, keep_in = {}, {} + for install_id, install_info in data["installed"].items(): + (reinstall if install_id in reinstall_on_restore else keep_in)[install_id] = install_info + data["installed"] = keep_in + data["to_install"].update(reinstall) + if data is not None: + tmpfile = tempfile.NamedTemporaryFile(delete_on_close=False) + with open(tmpfile.name, "w") as outfile: + json.dump(data, outfile) + add_to_archive(tmpfile.name, file_path) + return tmpfile.name + return None + + def _findRedownloadablePlugins(self, available_remote_plugins: frozenset) -> (frozenset[str], frozenset[str]): + """ Find all plugins that should be able to be reinstalled from the Marketplace. + + :param plugins_path: Path to all plugins in the user-space. + :return: Tuple of a set of plugin-ids and a set of plugin-paths. + """ + plugin_reg = PluginRegistry.getInstance() + id = "id" + plugins = [v for v in plugin_reg.getAllMetaData() + if v[id] in available_remote_plugins and not plugin_reg.isBundledPlugin(v[id])] + return frozenset([v[id] for v in plugins]), frozenset([v["location"] for v in plugins]) + + def _makeArchive(self, buffer: "io.BytesIO", root_path: str, available_remote_plugins: frozenset) -> Optional[ZipFile]: """Make a full archive from the given root path with the given name. :param root_path: The root directory to archive recursively. :return: The archive as bytes. """ ignore_string = re.compile("|".join(self.IGNORED_FILES + self.IGNORED_FOLDERS)) + reinstall_instead_ids, reinstall_instead_paths = self._findRedownloadablePlugins(available_remote_plugins) + tmpfiles = [] try: archive = ZipFile(buffer, "w", ZIP_DEFLATED) - for root, folders, files in os.walk(root_path): + add_path_to_archive = lambda path, alt_path: archive.write(path, alt_path[len(root_path) + len(os.sep):]) + for root, folders, files in os.walk(root_path, topdown=True): for item_name in folders + files: absolute_path = os.path.join(root, item_name) - if ignore_string.search(absolute_path): + if ignore_string.search(absolute_path) or any([absolute_path.startswith(x) for x in reinstall_instead_paths]): continue - archive.write(absolute_path, absolute_path[len(root_path) + len(os.sep):]) + if item_name == "packages.json": + tmpfiles.append( + self._fillToInstallsJson(absolute_path, reinstall_instead_ids, add_path_to_archive)) + else: + add_path_to_archive(absolute_path, absolute_path) archive.close() + for tmpfile_path in tmpfiles: + try: + os.remove(tmpfile_path) + except IOError as ex: + Logger.warning(f"Couldn't remove temporary file '{tmpfile_path}' because '{ex}'.") return archive except (IOError, OSError, BadZipfile) as error: Logger.log("e", "Could not create archive from user data directory: %s", error) diff --git a/cura/Backups/BackupsManager.py b/cura/Backups/BackupsManager.py index 6c4670edb6..67d6c84601 100644 --- a/cura/Backups/BackupsManager.py +++ b/cura/Backups/BackupsManager.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018 Ultimaker B.V. +# Copyright (c) 2025 UltiMaker # Cura is released under the terms of the LGPLv3 or higher. from typing import Dict, Optional, Tuple, TYPE_CHECKING @@ -22,7 +22,10 @@ class BackupsManager: def __init__(self, application: "CuraApplication") -> None: self._application = application - def createBackup(self) -> Tuple[Optional[bytes], Optional[Dict[str, str]]]: + def shouldReinstallDownloadablePlugins(self) -> bool: + return True + + def createBackup(self, available_remote_plugins: frozenset[str] = frozenset()) -> Tuple[Optional[bytes], Optional[Dict[str, str]]]: """ Get a back-up of the current configuration. @@ -31,17 +34,18 @@ class BackupsManager: self._disableAutoSave() backup = Backup(self._application) - backup.makeFromCurrent() + backup.makeFromCurrent(available_remote_plugins if self.shouldReinstallDownloadablePlugins() else frozenset()) self._enableAutoSave() # We don't return a Backup here because we want plugins only to interact with our API and not full objects. return backup.zip_file, backup.meta_data - def restoreBackup(self, zip_file: bytes, meta_data: Dict[str, str]) -> None: + def restoreBackup(self, zip_file: bytes, meta_data: Dict[str, str], auto_close: bool = True) -> None: """ Restore a back-up from a given ZipFile. :param zip_file: A bytes object containing the actual back-up. :param meta_data: A dict containing some metadata that is needed to restore the back-up correctly. + :param auto_close: Normally, Cura will need to close immediately after restoring the back-up. """ if not meta_data.get("cura_release", None): @@ -54,7 +58,7 @@ class BackupsManager: backup = Backup(self._application, zip_file = zip_file, meta_data = meta_data) restored = backup.restore() - if restored: + if restored and auto_close: # At this point, Cura will need to restart for the changes to take effect. # We don't want to store the data at this point as that would override the just-restored backup. self._application.windowClosed(save_data = False) diff --git a/packaging/NSIS/Ultimaker-Cura.nsi.jinja b/packaging/NSIS/Ultimaker-Cura.nsi.jinja index ac826af0d9..9f61e6950c 100644 --- a/packaging/NSIS/Ultimaker-Cura.nsi.jinja +++ b/packaging/NSIS/Ultimaker-Cura.nsi.jinja @@ -1,9 +1,8 @@ -# Copyright (c) 2022 UltiMaker B.V. +# Copyright (c) 2025 UltiMaker # Cura's build system is released under the terms of the AGPLv3 or higher. !define APP_NAME "{{ app_name }}" !define COMP_NAME "{{ company }}" -!define WEB_SITE "{{ web_site }}" !define VERSION "{{ version }}" !define VIVERSION "{{ version_major }}.{{ version_minor }}.{{ version_patch }}.0" !define COPYRIGHT "Copyright (c) {{ year }} {{ company }}" @@ -16,13 +15,11 @@ !define REG_APP_PATH "Software\Microsoft\Windows\CurrentVersion\App Paths\${APP_NAME}-${VERSION}" !define UNINSTALL_PATH "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}-${VERSION}" -!define REG_START_MENU "Start Menu Folder" +!define REG_START_MENU "Start Menu Shortcut" ;Require administrator access RequestExecutionLevel admin -var SM_Folder - ###################################################################### VIProductVersion "${VIVERSION}" @@ -64,11 +61,9 @@ InstallDir "$PROGRAMFILES64\${APP_NAME}" !ifdef REG_START_MENU !define MUI_STARTMENUPAGE_NODISABLE -!define MUI_STARTMENUPAGE_DEFAULTFOLDER "UltiMaker Cura" !define MUI_STARTMENUPAGE_REGISTRY_ROOT "${REG_ROOT}" !define MUI_STARTMENUPAGE_REGISTRY_KEY "${UNINSTALL_PATH}" !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "${REG_START_MENU}" -!insertmacro MUI_PAGE_STARTMENU Application $SM_Folder !endif !insertmacro MUI_PAGE_INSTFILES @@ -107,27 +102,11 @@ SetOutPath "$INSTDIR" WriteUninstaller "$INSTDIR\uninstall.exe" !ifdef REG_START_MENU -!insertmacro MUI_STARTMENU_WRITE_BEGIN Application -CreateDirectory "$SMPROGRAMS\$SM_Folder" -CreateShortCut "$SMPROGRAMS\$SM_Folder\${APP_NAME}.lnk" "$INSTDIR\${MAIN_APP_EXE}" -CreateShortCut "$SMPROGRAMS\$SM_Folder\Uninstall ${APP_NAME}.lnk" "$INSTDIR\uninstall.exe" - -!ifdef WEB_SITE -WriteIniStr "$INSTDIR\UltiMaker Cura website.url" "InternetShortcut" "URL" "${WEB_SITE}" -CreateShortCut "$SMPROGRAMS\$SM_Folder\UltiMaker Cura website.lnk" "$INSTDIR\UltiMaker Cura website.url" -!endif -!insertmacro MUI_STARTMENU_WRITE_END +CreateShortCut "$SMPROGRAMS\${APP_NAME}.lnk" "$INSTDIR\${MAIN_APP_EXE}" !endif !ifndef REG_START_MENU -CreateDirectory "$SMPROGRAMS\{{ app_name }}" -CreateShortCut "$SMPROGRAMS\{{ app_name }}\${APP_NAME}.lnk" "$INSTDIR\${MAIN_APP_EXE}" -CreateShortCut "$SMPROGRAMS\{{ app_name }}\Uninstall ${APP_NAME}.lnk" "$INSTDIR\uninstall.exe" - -!ifdef WEB_SITE -WriteIniStr "$INSTDIR\UltiMaker Cura website.url" "InternetShortcut" "URL" "${WEB_SITE}" -CreateShortCut "$SMPROGRAMS\{{ app_name }}\UltiMaker Cura website.lnk" "$INSTDIR\UltiMaker Cura website.url" -!endif +CreateShortCut "$SMPROGRAMS\${APP_NAME}.lnk" "$INSTDIR\${MAIN_APP_EXE}" !endif WriteRegStr ${REG_ROOT} "${REG_APP_PATH}" "" "$INSTDIR\${MAIN_APP_EXE}" @@ -138,9 +117,6 @@ WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "DisplayIcon" "$INSTDIR\${MAIN_APP_ WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "DisplayVersion" "${VERSION}" WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "Publisher" "${COMP_NAME}" -!ifdef WEB_SITE -WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "URLInfoAbout" "${WEB_SITE}" -!endif SectionEnd ###################################################################### @@ -177,29 +153,17 @@ RmDir "$INSTDIR\share\uranium" RmDir "$INSTDIR\share" Delete "$INSTDIR\uninstall.exe" -!ifdef WEB_SITE -Delete "$INSTDIR\${APP_NAME} website.url" -!endif RmDir /r /REBOOTOK "$INSTDIR" !ifdef REG_START_MENU -!insertmacro MUI_STARTMENU_GETFOLDER "Application" $SM_Folder -Delete "$SMPROGRAMS\$SM_Folder\${APP_NAME}.lnk" -Delete "$SMPROGRAMS\$SM_Folder\Uninstall ${APP_NAME}.lnk" -!ifdef WEB_SITE -Delete "$SMPROGRAMS\$SM_Folder\UltiMaker Cura website.lnk" -!endif -RmDir "$SMPROGRAMS\$SM_Folder" +Delete "$SMPROGRAMS\${APP_NAME}.lnk" +Delete "$SMPROGRAMS\Uninstall ${APP_NAME}.lnk" !endif !ifndef REG_START_MENU -Delete "$SMPROGRAMS\{{ app_name }}\${APP_NAME}.lnk" -Delete "$SMPROGRAMS\{{ app_name }}\Uninstall ${APP_NAME}.lnk" -!ifdef WEB_SITE -Delete "$SMPROGRAMS\{{ app_name }}\UltiMaker Cura website.lnk" -!endif -RmDir "$SMPROGRAMS\{{ app_name }}" +Delete "$SMPROGRAMS\${APP_NAME}.lnk" +Delete "$SMPROGRAMS\Uninstall ${APP_NAME}.lnk" !endif !insertmacro APP_UNASSOCIATE "stl" "Cura.model" diff --git a/packaging/NSIS/create_windows_installer.py b/packaging/NSIS/create_windows_installer.py index d15d62b951..e01c757dbb 100644 --- a/packaging/NSIS/create_windows_installer.py +++ b/packaging/NSIS/create_windows_installer.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 UltiMaker +# Copyright (c) 2025 UltiMaker # Cura is released under the terms of the LGPLv3 or higher. @@ -51,7 +51,6 @@ def generate_nsi(source_path: str, dist_path: str, filename: str, version: str): version_minor = str(parsed_version.minor), version_patch = str(parsed_version.patch), company = "UltiMaker", - web_site = "https://ultimaker.com", year = datetime.now().year, cura_license_file = str(source_loc.joinpath("packaging", "cura_license.txt")), compression_method = "LZMA", # ZLIB, BZIP2 or LZMA diff --git a/packaging/msi/create_windows_msi.py b/packaging/msi/create_windows_msi.py index e44a9a924b..12c64ed24f 100644 --- a/packaging/msi/create_windows_msi.py +++ b/packaging/msi/create_windows_msi.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 UltiMaker +# Copyright (c) 2025 UltiMaker # Cura is released under the terms of the LGPLv3 or higher. @@ -40,7 +40,6 @@ def generate_wxs(source_path: Path, dist_path: Path, filename: Path, app_name: s version_minor=str(parsed_version.minor), version_patch=str(parsed_version.patch), company="UltiMaker", - web_site="https://ultimaker.com", year=datetime.now().year, upgrade_code=str(uuid.uuid5(uuid.NAMESPACE_DNS, app_name)), cura_license_file=str(source_loc.joinpath("packaging", "msi", "cura_license.rtf")), diff --git a/plugins/CuraDrive/src/CreateBackupJob.py b/plugins/CuraDrive/src/CreateBackupJob.py index 7d772769ed..4820f886ab 100644 --- a/plugins/CuraDrive/src/CreateBackupJob.py +++ b/plugins/CuraDrive/src/CreateBackupJob.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Ultimaker B.V. +# Copyright (c) 2025 UltiMaker # Cura is released under the terms of the LGPLv3 or higher. import json import threading @@ -13,11 +13,14 @@ from UM.Message import Message from UM.TaskManagement.HttpRequestManager import HttpRequestManager from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope from UM.i18n import i18nCatalog +from cura.ApplicationMetadata import CuraSDKVersion from cura.CuraApplication import CuraApplication from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope +import cura.UltimakerCloud.UltimakerCloudConstants as UltimakerCloudConstants catalog = i18nCatalog("cura") +PACKAGES_URL = f"{UltimakerCloudConstants.CuraCloudAPIRoot}/cura-packages/v{UltimakerCloudConstants.CuraCloudAPIVersion}/cura/v{CuraSDKVersion}/packages" class CreateBackupJob(Job): """Creates backup zip, requests upload url and uploads the backup file to cloud storage.""" @@ -40,23 +43,54 @@ class CreateBackupJob(Job): self._job_done = threading.Event() """Set when the job completes. Does not indicate success.""" self.backup_upload_error_message = "" - """After the job completes, an empty string indicates success. Othrerwise, the value is a translated message.""" + """After the job completes, an empty string indicates success. Otherwise, the value is a translated message.""" + + def _setPluginFetchErrorMessage(self, error_msg: str) -> None: + Logger.error(f"Fetching plugins for backup resulted in error: {error_msg}") + self.backup_upload_error_message = "Couldn't update currently available plugins, backup stopped." + self._upload_message.hide() + self._job_done.set() def run(self) -> None: - upload_message = Message(catalog.i18nc("@info:backup_status", "Creating your backup..."), + self._upload_message = Message(catalog.i18nc("@info:backup_status", "Fetch re-downloadable package-ids..."), title = self.MESSAGE_TITLE, progress = -1) - upload_message.show() + self._upload_message.show() + CuraApplication.getInstance().processEvents() + + if CuraApplication.getInstance().getCuraAPI().backups.shouldReinstallDownloadablePlugins(): + request_url = f"{PACKAGES_URL}?package_type=plugin" + scope = JsonDecoratorScope(UltimakerCloudScope(CuraApplication.getInstance())) + HttpRequestManager.getInstance().get( + request_url, + scope=scope, + callback=self._continueRun, + error_callback=lambda reply, error: self._setPluginFetchErrorMessage(str(error)), + ) + else: + self._continueRun() + + def _continueRun(self, reply: "QNetworkReply" = None) -> None: + if reply is not None: + response_data = HttpRequestManager.readJSON(reply) + if "data" not in response_data: + self._setPluginFetchErrorMessage(f"Missing 'data' from response. Keys in response: {response_data.keys()}") + return + available_remote_plugins = frozenset({v["package_id"] for v in response_data["data"]}) + else: + available_remote_plugins = frozenset() + + self._upload_message.setText(catalog.i18nc("@info:backup_status", "Creating your backup...")) CuraApplication.getInstance().processEvents() cura_api = CuraApplication.getInstance().getCuraAPI() - self._backup_zip, backup_meta_data = cura_api.backups.createBackup() + self._backup_zip, backup_meta_data = cura_api.backups.createBackup(available_remote_plugins) if not self._backup_zip or not backup_meta_data: self.backup_upload_error_message = catalog.i18nc("@info:backup_status", "There was an error while creating your backup.") - upload_message.hide() + self._upload_message.hide() return - upload_message.setText(catalog.i18nc("@info:backup_status", "Uploading your backup...")) + self._upload_message.setText(catalog.i18nc("@info:backup_status", "Uploading your backup...")) CuraApplication.getInstance().processEvents() # Create an upload entry for the backup. @@ -64,13 +98,18 @@ class CreateBackupJob(Job): backup_meta_data["description"] = "{}.backup.{}.cura.zip".format(timestamp, backup_meta_data["cura_release"]) self._requestUploadSlot(backup_meta_data, len(self._backup_zip)) - self._job_done.wait() + # Note: One 'process events' call wasn't enough with the changed situation somehow. + for _ in range(5000): + CuraApplication.getInstance().processEvents() + if self._job_done.wait(0.02): + break + if self.backup_upload_error_message == "": - upload_message.setText(catalog.i18nc("@info:backup_status", "Your backup has finished uploading.")) - upload_message.setProgress(None) # Hide progress bar + self._upload_message.setText(catalog.i18nc("@info:backup_status", "Your backup has finished uploading.")) + self._upload_message.setProgress(None) # Hide progress bar else: # some error occurred. This error is presented to the user by DrivePluginExtension - upload_message.hide() + self._upload_message.hide() def _requestUploadSlot(self, backup_metadata: Dict[str, Any], backup_size: int) -> None: """Request a backup upload slot from the API. @@ -83,7 +122,6 @@ class CreateBackupJob(Job): "metadata": backup_metadata } }).encode() - HttpRequestManager.getInstance().put( self._api_backup_url, data = payload, diff --git a/plugins/CuraDrive/src/RestoreBackupJob.py b/plugins/CuraDrive/src/RestoreBackupJob.py index 54c94b389e..503b39547a 100644 --- a/plugins/CuraDrive/src/RestoreBackupJob.py +++ b/plugins/CuraDrive/src/RestoreBackupJob.py @@ -1,8 +1,9 @@ -# Copyright (c) 2021 Ultimaker B.V. +# Copyright (c) 2025 UltiMaker # Cura is released under the terms of the LGPLv3 or higher. - import base64 import hashlib +import json +import os import threading from tempfile import NamedTemporaryFile from typing import Optional, Any, Dict @@ -12,9 +13,16 @@ from PyQt6.QtNetwork import QNetworkReply, QNetworkRequest from UM.Job import Job from UM.Logger import Logger from UM.PackageManager import catalog +from UM.Resources import Resources from UM.TaskManagement.HttpRequestManager import HttpRequestManager -from cura.CuraApplication import CuraApplication +from UM.Version import Version +from cura.ApplicationMetadata import CuraSDKVersion +from cura.CuraApplication import CuraApplication +from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope +import cura.UltimakerCloud.UltimakerCloudConstants as UltimakerCloudConstants + +PACKAGES_URL_TEMPLATE = f"{UltimakerCloudConstants.CuraCloudAPIRoot}/cura-packages/v{UltimakerCloudConstants.CuraCloudAPIVersion}/cura/v{{0}}/packages/{{1}}/download" class RestoreBackupJob(Job): """Downloads a backup and overwrites local configuration with the backup. @@ -38,7 +46,6 @@ class RestoreBackupJob(Job): self.restore_backup_error_message = "" def run(self) -> None: - url = self._backup.get("download_url") assert url is not None @@ -48,7 +55,11 @@ class RestoreBackupJob(Job): error_callback = self._onRestoreRequestCompleted ) - self._job_done.wait() # A job is considered finished when the run function completes + # Note: Just to be sure, use the same structure here as in CreateBackupJob. + for _ in range(5000): + CuraApplication.getInstance().processEvents() + if self._job_done.wait(0.02): + break def _onRestoreRequestCompleted(self, reply: QNetworkReply, error: Optional["QNetworkReply.NetworkError"] = None) -> None: if not HttpRequestManager.replyIndicatesSuccess(reply, error): @@ -60,8 +71,8 @@ class RestoreBackupJob(Job): # We store the file in a temporary path fist to ensure integrity. try: - temporary_backup_file = NamedTemporaryFile(delete = False) - with open(temporary_backup_file.name, "wb") as write_backup: + self._temporary_backup_file = NamedTemporaryFile(delete_on_close = False) + with open(self._temporary_backup_file.name, "wb") as write_backup: app = CuraApplication.getInstance() bytes_read = reply.read(self.DISK_WRITE_BUFFER_SIZE) while bytes_read: @@ -69,23 +80,98 @@ class RestoreBackupJob(Job): bytes_read = reply.read(self.DISK_WRITE_BUFFER_SIZE) app.processEvents() except EnvironmentError as e: - Logger.log("e", f"Unable to save backed up files due to computer limitations: {str(e)}") + Logger.error(f"Unable to save backed up files due to computer limitations: {str(e)}") self.restore_backup_error_message = self.DEFAULT_ERROR_MESSAGE self._job_done.set() return - if not self._verifyMd5Hash(temporary_backup_file.name, self._backup.get("md5_hash", "")): + if not self._verifyMd5Hash(self._temporary_backup_file.name, self._backup.get("md5_hash", "")): # Don't restore the backup if the MD5 hashes do not match. # This can happen if the download was interrupted. - Logger.log("w", "Remote and local MD5 hashes do not match, not restoring backup.") + Logger.error("Remote and local MD5 hashes do not match, not restoring backup.") self.restore_backup_error_message = self.DEFAULT_ERROR_MESSAGE + self._job_done.set() + return # Tell Cura to place the backup back in the user data folder. - with open(temporary_backup_file.name, "rb") as read_backup: + metadata = self._backup.get("metadata", {}) + with open(self._temporary_backup_file.name, "rb") as read_backup: cura_api = CuraApplication.getInstance().getCuraAPI() - cura_api.backups.restoreBackup(read_backup.read(), self._backup.get("metadata", {})) + cura_api.backups.restoreBackup(read_backup.read(), metadata, auto_close=False) - self._job_done.set() + # Read packages data-file, to get the 'to_install' plugin-ids. + version_to_restore = Version(metadata.get("cura_release", "dev")) + version_str = f"{version_to_restore.getMajor()}.{version_to_restore.getMinor()}" + packages_path = os.path.abspath(os.path.join(os.path.abspath( + Resources.getConfigStoragePath()), "..", version_str, "packages.json")) + if not os.path.exists(packages_path): + Logger.error(f"Can't find path '{packages_path}' to tell what packages should be redownloaded.") + self.restore_backup_error_message = self.DEFAULT_ERROR_MESSAGE + self._job_done.set() + return + + to_install = {} + try: + with open(packages_path, "r") as packages_file: + packages_json = json.load(packages_file) + if "to_install" in packages_json: + for package_data in packages_json["to_install"].values(): + if "package_info" not in package_data: + continue + package_info = package_data["package_info"] + if "package_id" in package_info and "sdk_version_semver" in package_info: + to_install[package_info["package_id"]] = package_info["sdk_version_semver"] + except IOError as ex: + Logger.error(f"Couldn't open '{packages_path}' because '{str(ex)}' to get packages to re-install.") + self.restore_backup_error_message = self.DEFAULT_ERROR_MESSAGE + self._job_done.set() + return + + if len(to_install) < 1: + Logger.info("No packages to reinstall, early out.") + self._job_done.set() + return + + # Download all re-installable plugins packages, so they can be put back on start-up. + redownload_errors = [] + def packageDownloadCallback(package_id: str, msg: "QNetworkReply", err: "QNetworkReply.NetworkError" = None) -> None: + if err is not None or HttpRequestManager.safeHttpStatus(msg) != 200: + redownload_errors.append(err) + del to_install[package_id] + + try: + with NamedTemporaryFile(mode="wb", suffix=".curapackage", delete=False) as temp_file: + bytes_read = msg.read(self.DISK_WRITE_BUFFER_SIZE) + while bytes_read: + temp_file.write(bytes_read) + bytes_read = msg.read(self.DISK_WRITE_BUFFER_SIZE) + CuraApplication.getInstance().processEvents() + temp_file.close() + if not CuraApplication.getInstance().getPackageManager().installPackage(temp_file.name): + redownload_errors.append(f"Couldn't install package '{package_id}'.") + except IOError as ex: + redownload_errors.append(f"Couldn't process package '{package_id}' because '{ex}'.") + + if len(to_install) < 1: + if len(redownload_errors) == 0: + Logger.info("All packages redownloaded!") + self._job_done.set() + else: + msgs = "\n - ".join(redownload_errors) + Logger.error(f"Couldn't re-install at least one package(s) because: {msgs}") + self.restore_backup_error_message = self.DEFAULT_ERROR_MESSAGE + self._job_done.set() + + self._package_download_scope = UltimakerCloudScope(CuraApplication.getInstance()) + for package_id, package_api_version in to_install.items(): + def handlePackageId(package_id: str = package_id): + HttpRequestManager.getInstance().get( + PACKAGES_URL_TEMPLATE.format(package_api_version, package_id), + scope=self._package_download_scope, + callback=lambda msg: packageDownloadCallback(package_id, msg), + error_callback=lambda msg, err: packageDownloadCallback(package_id, msg, err) + ) + handlePackageId(package_id) @staticmethod def _verifyMd5Hash(file_path: str, known_hash: str) -> bool: diff --git a/resources/definitions/ultimaker_s6.def.json b/resources/definitions/ultimaker_s6.def.json new file mode 100644 index 0000000000..bc0e6a0f4e --- /dev/null +++ b/resources/definitions/ultimaker_s6.def.json @@ -0,0 +1,53 @@ +{ + "version": 2, + "name": "UltiMaker S6", + "inherits": "ultimaker_s8", + "metadata": + { + "visible": true, + "author": "UltiMaker", + "manufacturer": "Ultimaker B.V.", + "file_formats": "application/x-ufp;text/x-gcode", + "platform": "ultimaker_s5_platform.obj", + "bom_numbers": [ + 10700 + ], + "firmware_update_info": + { + "check_urls": [ "https://software.ultimaker.com/releases/firmware/5078167/stable/um-update.swu.version" ], + "id": 5078167, + "update_url": "https://ultimaker.com/firmware?utm_source=cura&utm_medium=software&utm_campaign=fw-update" + }, + "first_start_actions": [ "DiscoverUM3Action" ], + "has_machine_quality": true, + "has_materials": true, + "has_variants": true, + "machine_extruder_trains": + { + "0": "ultimaker_s6_extruder_left", + "1": "ultimaker_s6_extruder_right" + }, + "nozzle_offsetting_for_disallowed_areas": false, + "platform_offset": [ + 0, + -30, + -10 + ], + "platform_texture": "UltimakerS6backplate.png", + "preferred_material": "ultimaker_pla_blue", + "preferred_variant_name": "AA+ 0.4", + "quality_definition": "ultimaker_s8", + "supported_actions": [ "DiscoverUM3Action" ], + "supports_material_export": true, + "supports_network_connection": true, + "supports_usb_connection": false, + "variants_name": "Print Core", + "variants_name_has_translation": true, + "weight": -2 + }, + "overrides": + { + "adhesion_type": { "value": "'brim'" }, + "machine_name": { "default_value": "UltiMaker S6" } + } +} \ No newline at end of file diff --git a/resources/definitions/ultimaker_s7.def.json b/resources/definitions/ultimaker_s7.def.json index 14d9b21168..41120c23df 100644 --- a/resources/definitions/ultimaker_s7.def.json +++ b/resources/definitions/ultimaker_s7.def.json @@ -47,7 +47,7 @@ "overrides": { "default_material_print_temperature": { "maximum_value_warning": "320" }, - "machine_name": { "default_value": "Ultimaker S7" }, + "machine_name": { "default_value": "UltiMaker S7" }, "material_print_temperature_layer_0": { "maximum_value_warning": "320" } } } \ No newline at end of file diff --git a/resources/definitions/ultimaker_s8.def.json b/resources/definitions/ultimaker_s8.def.json index 80e7986acf..8fa2ab8459 100644 --- a/resources/definitions/ultimaker_s8.def.json +++ b/resources/definitions/ultimaker_s8.def.json @@ -385,7 +385,7 @@ "unit": "m/s\u00b3", "value": "20000 if machine_gcode_flavor == 'Cheetah' else 100" }, - "machine_name": { "default_value": "Ultimaker S8" }, + "machine_name": { "default_value": "UltiMaker S8" }, "machine_nozzle_cool_down_speed": { "default_value": 1.3 }, "machine_nozzle_heat_up_speed": { "default_value": 0.6 }, "machine_start_gcode": { "default_value": "M213 U0.1 ;undercut 0.1mm" }, @@ -412,7 +412,7 @@ "retraction_hop": { "value": 1 }, "retraction_hop_after_extruder_switch_height": { "value": 2 }, "retraction_hop_enabled": { "value": true }, - "retraction_min_travel": { "value": "5 if support_enable and support_structure=='tree' else line_width * 2" }, + "retraction_min_travel": { "value": "5 if support_enable and support_structure=='tree' else line_width * 2.5" }, "retraction_prime_speed": { "value": 15 }, "skin_edge_support_thickness": { "value": 0 }, "skin_material_flow": { "value": 95 }, diff --git a/resources/extruders/ultimaker_s6_extruder_left.def.json b/resources/extruders/ultimaker_s6_extruder_left.def.json new file mode 100644 index 0000000000..d3991222b2 --- /dev/null +++ b/resources/extruders/ultimaker_s6_extruder_left.def.json @@ -0,0 +1,31 @@ +{ + "version": 2, + "name": "Extruder 1", + "inherits": "fdmextruder", + "metadata": + { + "machine": "ultimaker_s6", + "position": "0" + }, + "overrides": + { + "extruder_nr": + { + "default_value": 0, + "maximum_value": "1" + }, + "extruder_prime_pos_x": { "default_value": -3 }, + "extruder_prime_pos_y": { "default_value": 6 }, + "extruder_prime_pos_z": { "default_value": 2 }, + "machine_extruder_end_pos_abs": { "default_value": true }, + "machine_extruder_end_pos_x": { "default_value": 330 }, + "machine_extruder_end_pos_y": { "default_value": 237 }, + "machine_extruder_start_code": { "value": "\"M214 D0 K{material_pressure_advance_factor} R0.04\"" }, + "machine_extruder_start_pos_abs": { "default_value": true }, + "machine_extruder_start_pos_x": { "default_value": 330 }, + "machine_extruder_start_pos_y": { "default_value": 237 }, + "machine_nozzle_head_distance": { "default_value": 2.7 }, + "machine_nozzle_offset_x": { "default_value": 0 }, + "machine_nozzle_offset_y": { "default_value": 0 } + } +} \ No newline at end of file diff --git a/resources/extruders/ultimaker_s6_extruder_right.def.json b/resources/extruders/ultimaker_s6_extruder_right.def.json new file mode 100644 index 0000000000..5c70f36741 --- /dev/null +++ b/resources/extruders/ultimaker_s6_extruder_right.def.json @@ -0,0 +1,31 @@ +{ + "version": 2, + "name": "Extruder 2", + "inherits": "fdmextruder", + "metadata": + { + "machine": "ultimaker_s6", + "position": "1" + }, + "overrides": + { + "extruder_nr": + { + "default_value": 1, + "maximum_value": "1" + }, + "extruder_prime_pos_x": { "default_value": 333 }, + "extruder_prime_pos_y": { "default_value": 6 }, + "extruder_prime_pos_z": { "default_value": 2 }, + "machine_extruder_end_pos_abs": { "default_value": true }, + "machine_extruder_end_pos_x": { "default_value": 330 }, + "machine_extruder_end_pos_y": { "default_value": 219 }, + "machine_extruder_start_code": { "value": "\"M214 D0 K{material_pressure_advance_factor} R0.04\"" }, + "machine_extruder_start_pos_abs": { "default_value": true }, + "machine_extruder_start_pos_x": { "default_value": 330 }, + "machine_extruder_start_pos_y": { "default_value": 219 }, + "machine_nozzle_head_distance": { "default_value": 4.2 }, + "machine_nozzle_offset_x": { "default_value": 22 }, + "machine_nozzle_offset_y": { "default_value": 0 } + } +} \ No newline at end of file diff --git a/resources/images/UltimakerS6backplate.png b/resources/images/UltimakerS6backplate.png new file mode 100644 index 0000000000..d6e83781cc Binary files /dev/null and b/resources/images/UltimakerS6backplate.png differ diff --git a/resources/intent/ultimaker_s8/um_s8_cc_plus_0.6_nylon-cf-slide_0.2mm_engineering.inst.cfg b/resources/intent/ultimaker_s8/um_s8_cc_plus_0.6_nylon-cf-slide_0.2mm_engineering.inst.cfg new file mode 100644 index 0000000000..88a2326843 --- /dev/null +++ b/resources/intent/ultimaker_s8/um_s8_cc_plus_0.6_nylon-cf-slide_0.2mm_engineering.inst.cfg @@ -0,0 +1,18 @@ +[general] +definition = ultimaker_s8 +name = Accurate +version = 4 + +[metadata] +intent_category = engineering +material = generic_nylon-cf-slide +quality_type = draft +setting_version = 25 +type = intent +variant = CC+ 0.6 + +[values] +infill_sparse_density = 20 +top_bottom_thickness = =wall_thickness +wall_thickness = =line_width * 3 + diff --git a/resources/intent/ultimaker_s8/um_s8_aa_plus_0.4_nylon_0.2mm_engineering.inst.cfg b/resources/intent/ultimaker_s8/um_s8_cc_plus_0.6_petcf_0.2mm_engineering.inst.cfg similarity index 86% rename from resources/intent/ultimaker_s8/um_s8_aa_plus_0.4_nylon_0.2mm_engineering.inst.cfg rename to resources/intent/ultimaker_s8/um_s8_cc_plus_0.6_petcf_0.2mm_engineering.inst.cfg index 2878f4df54..dc9870b7ff 100644 --- a/resources/intent/ultimaker_s8/um_s8_aa_plus_0.4_nylon_0.2mm_engineering.inst.cfg +++ b/resources/intent/ultimaker_s8/um_s8_cc_plus_0.6_petcf_0.2mm_engineering.inst.cfg @@ -5,11 +5,11 @@ version = 4 [metadata] intent_category = engineering -material = generic_nylon +material = generic_petcf quality_type = draft setting_version = 25 type = intent -variant = AA+ 0.4 +variant = CC+ 0.6 [values] infill_sparse_density = 20 diff --git a/resources/qml/Preferences/GeneralPage.qml b/resources/qml/Preferences/GeneralPage.qml index 62cab53a78..42469c6cf6 100644 --- a/resources/qml/Preferences/GeneralPage.qml +++ b/resources/qml/Preferences/GeneralPage.qml @@ -360,17 +360,6 @@ UM.PreferencesPage } } - UM.Label - { - id: languageCaption - - //: Language change warning - text: catalog.i18nc("@label", "*You will need to restart the application for these changes to have effect.") - wrapMode: Text.WordWrap - font.italic: true - - } - Item { //: Spacer @@ -705,7 +694,7 @@ UM.PreferencesPage UM.CheckBox { id: singleInstanceCheckbox - text: catalog.i18nc("@option:check","Use a single instance of Cura") + text: catalog.i18nc("@option:check","Use a single instance of Cura *") checked: boolCheck(UM.Preferences.getValue("cura/single_instance")) onCheckedChanged: UM.Preferences.setValue("cura/single_instance", checked) @@ -1101,8 +1090,6 @@ UM.PreferencesPage } } - - /* Multi-buildplate functionality is disabled because it's broken. See CURA-4975 for the ticket to remove it. Item { //: Spacer @@ -1110,6 +1097,18 @@ UM.PreferencesPage width: UM.Theme.getSize("default_margin").height } + UM.Label + { + id: languageCaption + + //: Language change warning + text: catalog.i18nc("@label", "*You will need to restart the application for these changes to have effect.") + wrapMode: Text.WordWrap + font.italic: true + } + + /* Multi-buildplate functionality is disabled because it's broken. See CURA-4975 for the ticket to remove it. + Label { font.bold: true diff --git a/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_abs_0.2mm.inst.cfg b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_abs_0.2mm.inst.cfg index 8c76290ed3..37767673aa 100644 --- a/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_abs_0.2mm.inst.cfg +++ b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_abs_0.2mm.inst.cfg @@ -15,7 +15,6 @@ weight = -2 cool_min_layer_time = 4 cool_min_layer_time_fan_speed_max = 9 cool_min_temperature = =material_print_temperature - 10 -material_print_temperature = =default_material_print_temperature + 5 retraction_prime_speed = 15 support_structure = tree diff --git a/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_cpe_0.2mm.inst.cfg b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_cpe_0.2mm.inst.cfg index 25a277a06c..bff86d6fa4 100644 --- a/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_cpe_0.2mm.inst.cfg +++ b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.4_cpe_0.2mm.inst.cfg @@ -13,8 +13,10 @@ weight = -2 [values] infill_overlap = 20 -infill_pattern = ='zigzag' if infill_sparse_density > 80 else 'gyroid' -speed_print = 100 -speed_wall_0 = =speed_print +infill_pattern = lines +speed_print = 40 +speed_wall = =speed_print +speed_wall_0 = =speed_wall support_interface_enable = True +wall_thickness = =wall_line_width_0 + 2*wall_line_width_x diff --git a/resources/quality/ultimaker_s8/um_s8_aa_plus_0.6_abs_0.2mm.inst.cfg b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.6_abs_0.2mm.inst.cfg new file mode 100644 index 0000000000..928293a327 --- /dev/null +++ b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.6_abs_0.2mm.inst.cfg @@ -0,0 +1,23 @@ +[general] +definition = ultimaker_s8 +name = Fast +version = 4 + +[metadata] +material = generic_abs +quality_type = draft +setting_version = 25 +type = quality +variant = AA+ 0.6 +weight = -2 + +[values] +bridge_skin_material_flow = 200 +bridge_wall_material_flow = 200 +cool_min_layer_time = 4 +cool_min_layer_time_fan_speed_max = 9 +cool_min_temperature = =material_print_temperature - 10 +retraction_prime_speed = 15 +support_interface_enable = False +support_structure = tree + diff --git a/resources/quality/ultimaker_s8/um_s8_aa_plus_0.6_abs_0.3mm.inst.cfg b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.6_abs_0.3mm.inst.cfg new file mode 100644 index 0000000000..a5a83362cc --- /dev/null +++ b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.6_abs_0.3mm.inst.cfg @@ -0,0 +1,25 @@ +[general] +definition = ultimaker_s8 +name = Extra Fast +version = 4 + +[metadata] +material = generic_abs +quality_type = verydraft +setting_version = 25 +type = quality +variant = AA+ 0.6 +weight = -3 + +[values] +bridge_skin_material_flow = 200 +bridge_wall_material_flow = 200 +cool_min_layer_time = 4 +cool_min_layer_time_fan_speed_max = 9 +cool_min_temperature = =material_print_temperature - 10 +material_print_temperature = =default_material_print_temperature + 10 +retraction_prime_speed = 15 +support_interface_enable = False +support_structure = tree +wall_line_width_0 = =line_width * (1 + magic_spiralize * 0.25) + diff --git a/resources/quality/ultimaker_s8/um_s8_aa_plus_0.6_petg_0.2mm.inst.cfg b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.6_petg_0.2mm.inst.cfg new file mode 100644 index 0000000000..be16bb734f --- /dev/null +++ b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.6_petg_0.2mm.inst.cfg @@ -0,0 +1,20 @@ +[general] +definition = ultimaker_s8 +name = Fast +version = 4 + +[metadata] +material = generic_petg +quality_type = draft +setting_version = 25 +type = quality +variant = AA+ 0.6 +weight = -2 + +[values] +bridge_skin_material_flow = 200 +bridge_wall_material_flow = 200 +cool_min_layer_time = 4 +material_print_temperature = =default_material_print_temperature + 5 +support_interface_enable = False + diff --git a/resources/quality/ultimaker_s8/um_s8_aa_plus_0.6_petg_0.3mm.inst.cfg b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.6_petg_0.3mm.inst.cfg new file mode 100644 index 0000000000..d058e1aef5 --- /dev/null +++ b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.6_petg_0.3mm.inst.cfg @@ -0,0 +1,21 @@ +[general] +definition = ultimaker_s8 +name = Extra Fast +version = 4 + +[metadata] +material = generic_petg +quality_type = verydraft +setting_version = 25 +type = quality +variant = AA+ 0.6 +weight = -3 + +[values] +bridge_skin_material_flow = 200 +bridge_wall_material_flow = 200 +cool_min_layer_time = 4 +material_print_temperature = =default_material_print_temperature + 10 +support_interface_enable = False +wall_line_width_0 = =line_width * (1 + magic_spiralize * 0.25) + diff --git a/resources/quality/ultimaker_s8/um_s8_aa_plus_0.6_pla_0.2mm.inst.cfg b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.6_pla_0.2mm.inst.cfg new file mode 100644 index 0000000000..a5f990f3d8 --- /dev/null +++ b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.6_pla_0.2mm.inst.cfg @@ -0,0 +1,20 @@ +[general] +definition = ultimaker_s8 +name = Fast +version = 4 + +[metadata] +material = generic_pla +quality_type = draft +setting_version = 25 +type = quality +variant = AA+ 0.6 +weight = -2 + +[values] +bridge_skin_material_flow = 200 +bridge_wall_material_flow = 200 +retraction_prime_speed = =retraction_speed +support_interface_enable = False +support_structure = tree + diff --git a/resources/quality/ultimaker_s8/um_s8_aa_plus_0.6_pla_0.3mm.inst.cfg b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.6_pla_0.3mm.inst.cfg new file mode 100644 index 0000000000..295b7efbfc --- /dev/null +++ b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.6_pla_0.3mm.inst.cfg @@ -0,0 +1,22 @@ +[general] +definition = ultimaker_s8 +name = Extra Fast +version = 4 + +[metadata] +material = generic_pla +quality_type = verydraft +setting_version = 25 +type = quality +variant = AA+ 0.6 +weight = -3 + +[values] +bridge_skin_material_flow = 200 +bridge_wall_material_flow = 200 +material_print_temperature = =default_material_print_temperature + 20 +retraction_prime_speed = =retraction_speed +support_interface_enable = False +support_structure = tree +wall_line_width_0 = =line_width * (1 + magic_spiralize * 0.25) + diff --git a/resources/quality/ultimaker_s8/um_s8_aa_plus_0.6_tough-pla_0.2mm.inst.cfg b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.6_tough-pla_0.2mm.inst.cfg new file mode 100644 index 0000000000..f934c2bfa0 --- /dev/null +++ b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.6_tough-pla_0.2mm.inst.cfg @@ -0,0 +1,20 @@ +[general] +definition = ultimaker_s8 +name = Fast +version = 4 + +[metadata] +material = generic_tough_pla +quality_type = draft +setting_version = 25 +type = quality +variant = AA+ 0.6 +weight = -2 + +[values] +bridge_skin_material_flow = 200 +bridge_wall_material_flow = 200 +retraction_prime_speed = =retraction_speed +support_interface_enable = False +support_structure = tree + diff --git a/resources/quality/ultimaker_s8/um_s8_aa_plus_0.6_tough-pla_0.3mm.inst.cfg b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.6_tough-pla_0.3mm.inst.cfg new file mode 100644 index 0000000000..aa81282888 --- /dev/null +++ b/resources/quality/ultimaker_s8/um_s8_aa_plus_0.6_tough-pla_0.3mm.inst.cfg @@ -0,0 +1,22 @@ +[general] +definition = ultimaker_s8 +name = Extra Fast +version = 4 + +[metadata] +material = generic_tough_pla +quality_type = verydraft +setting_version = 25 +type = quality +variant = AA+ 0.6 +weight = -3 + +[values] +bridge_skin_material_flow = 200 +bridge_wall_material_flow = 200 +material_print_temperature = =default_material_print_temperature + 20 +retraction_prime_speed = =retraction_speed +support_interface_enable = False +support_structure = tree +wall_line_width_0 = =line_width * (1 + magic_spiralize * 0.25) + diff --git a/resources/quality/ultimaker_s8/um_s8_bb0.4_pva_0.15mm.inst.cfg b/resources/quality/ultimaker_s8/um_s8_bb0.4_pva_0.15mm.inst.cfg index 9539bd42b1..b2e787724e 100644 --- a/resources/quality/ultimaker_s8/um_s8_bb0.4_pva_0.15mm.inst.cfg +++ b/resources/quality/ultimaker_s8/um_s8_bb0.4_pva_0.15mm.inst.cfg @@ -13,17 +13,23 @@ weight = -1 [values] acceleration_prime_tower = 1500 +acceleration_support = 1500 brim_replaces_support = False build_volume_temperature = =70 if extruders_enabled_count > 1 else 35 cool_fan_enabled = =not (support_enable and (extruder_nr == support_infill_extruder_nr)) default_material_bed_temperature = =0 if extruders_enabled_count > 1 else 60 initial_layer_line_width_factor = 150 +jerk_prime_tower = 4000 +jerk_support = 4000 minimum_support_area = 4 +retraction_amount = 6.5 retraction_count_max = 5 skirt_brim_minimal_length = =min(2000, 175 / (layer_height * line_width)) -speed_prime_tower = 25 +speed_prime_tower = 50 speed_support = 50 -support_angle = 45 +speed_support_bottom = =2*speed_support_interface/5 +speed_support_interface = 50 +support_bottom_density = 70 support_infill_sparse_thickness = =2 * layer_height support_interface_enable = True support_z_distance = 0 diff --git a/resources/quality/ultimaker_s8/um_s8_bb0.4_pva_0.1mm.inst.cfg b/resources/quality/ultimaker_s8/um_s8_bb0.4_pva_0.1mm.inst.cfg index d5e6084c76..a6d3010f60 100644 --- a/resources/quality/ultimaker_s8/um_s8_bb0.4_pva_0.1mm.inst.cfg +++ b/resources/quality/ultimaker_s8/um_s8_bb0.4_pva_0.1mm.inst.cfg @@ -13,18 +13,24 @@ weight = 0 [values] acceleration_prime_tower = 1500 +acceleration_support = 1500 brim_replaces_support = False build_volume_temperature = =70 if extruders_enabled_count > 1 else 35 cool_fan_enabled = =not (support_enable and (extruder_nr == support_infill_extruder_nr)) default_material_bed_temperature = =0 if extruders_enabled_count > 1 else 60 initial_layer_line_width_factor = 150 +jerk_prime_tower = 4000 +jerk_support = 4000 material_print_temperature = =default_material_print_temperature - 5 minimum_support_area = 4 +retraction_amount = 6.5 retraction_count_max = 5 skirt_brim_minimal_length = =min(2000, 175 / (layer_height * line_width)) -speed_prime_tower = 25 +speed_prime_tower = 50 speed_support = 50 -support_angle = 45 +speed_support_bottom = =2*speed_support_interface/5 +speed_support_interface = 50 +support_bottom_density = 70 support_infill_sparse_thickness = =2 * layer_height support_interface_enable = True support_z_distance = 0 diff --git a/resources/quality/ultimaker_s8/um_s8_bb0.4_pva_0.2mm.inst.cfg b/resources/quality/ultimaker_s8/um_s8_bb0.4_pva_0.2mm.inst.cfg index 3c29ca8186..9994a7c503 100644 --- a/resources/quality/ultimaker_s8/um_s8_bb0.4_pva_0.2mm.inst.cfg +++ b/resources/quality/ultimaker_s8/um_s8_bb0.4_pva_0.2mm.inst.cfg @@ -13,18 +13,24 @@ weight = -2 [values] acceleration_prime_tower = 1500 +acceleration_support = 1500 brim_replaces_support = False build_volume_temperature = =70 if extruders_enabled_count > 1 else 35 cool_fan_enabled = =not (support_enable and (extruder_nr == support_infill_extruder_nr)) default_material_bed_temperature = =0 if extruders_enabled_count > 1 else 60 initial_layer_line_width_factor = 150 +jerk_prime_tower = 4000 +jerk_support = 4000 material_print_temperature = =default_material_print_temperature + 5 minimum_support_area = 4 +retraction_amount = 6.5 retraction_count_max = 5 skirt_brim_minimal_length = =min(2000, 175 / (layer_height * line_width)) -speed_prime_tower = 25 +speed_prime_tower = 50 speed_support = 50 -support_angle = 45 +speed_support_bottom = =2*speed_support_interface/5 +speed_support_interface = 50 +support_bottom_density = 70 support_interface_enable = True support_z_distance = 0 diff --git a/resources/quality/ultimaker_s8/um_s8_bb0.4_pva_0.3mm.inst.cfg b/resources/quality/ultimaker_s8/um_s8_bb0.4_pva_0.3mm.inst.cfg index a49cea6817..4c9ca9c2cc 100644 --- a/resources/quality/ultimaker_s8/um_s8_bb0.4_pva_0.3mm.inst.cfg +++ b/resources/quality/ultimaker_s8/um_s8_bb0.4_pva_0.3mm.inst.cfg @@ -13,18 +13,24 @@ weight = -3 [values] acceleration_prime_tower = 1500 +acceleration_support = 1500 brim_replaces_support = False build_volume_temperature = =70 if extruders_enabled_count > 1 else 35 cool_fan_enabled = =not (support_enable and (extruder_nr == support_infill_extruder_nr)) default_material_bed_temperature = =0 if extruders_enabled_count > 1 else 60 initial_layer_line_width_factor = 150 +jerk_prime_tower = 4000 +jerk_support = 4000 material_print_temperature = =default_material_print_temperature - 5 minimum_support_area = 4 +retraction_amount = 6.5 retraction_count_max = 5 skirt_brim_minimal_length = =min(2000, 175 / (layer_height * line_width)) -speed_prime_tower = 25 +speed_prime_tower = 50 speed_support = 50 -support_angle = 45 +speed_support_bottom = =2*speed_support_interface/5 +speed_support_interface = 50 +support_bottom_density = 70 support_infill_sparse_thickness = 0.3 support_interface_enable = True support_z_distance = 0 diff --git a/resources/quality/ultimaker_s8/um_s8_cc_plus_0.4_pc_0.2mm.inst.cfg b/resources/quality/ultimaker_s8/um_s8_cc_plus_0.4_pc_0.2mm.inst.cfg index f6c91375f9..ae64e07f03 100644 --- a/resources/quality/ultimaker_s8/um_s8_cc_plus_0.4_pc_0.2mm.inst.cfg +++ b/resources/quality/ultimaker_s8/um_s8_cc_plus_0.4_pc_0.2mm.inst.cfg @@ -14,6 +14,8 @@ weight = -2 [values] cool_min_layer_time = 6 cool_min_layer_time_fan_speed_max = 12 -retraction_amount = 8 +inset_direction = inside_out +material_flow = 95 retraction_prime_speed = 15 +speed_wall_x = =speed_wall_0 diff --git a/resources/quality/ultimaker_s8/um_s8_cc_plus_0.6_nylon-cf-slide_0.2mm.inst.cfg b/resources/quality/ultimaker_s8/um_s8_cc_plus_0.6_nylon-cf-slide_0.2mm.inst.cfg new file mode 100644 index 0000000000..195c11a4e0 --- /dev/null +++ b/resources/quality/ultimaker_s8/um_s8_cc_plus_0.6_nylon-cf-slide_0.2mm.inst.cfg @@ -0,0 +1,17 @@ +[general] +definition = ultimaker_s8 +name = Fast +version = 4 + +[metadata] +material = generic_nylon-cf-slide +quality_type = draft +setting_version = 25 +type = quality +variant = CC+ 0.6 +weight = -2 + +[values] +cool_min_layer_time_fan_speed_max = 11 +retraction_prime_speed = 15 + diff --git a/resources/quality/ultimaker_s8/um_s8_cc_plus_0.6_nylon-cf-slide_0.3mm.inst.cfg b/resources/quality/ultimaker_s8/um_s8_cc_plus_0.6_nylon-cf-slide_0.3mm.inst.cfg new file mode 100644 index 0000000000..4f4f7f7eff --- /dev/null +++ b/resources/quality/ultimaker_s8/um_s8_cc_plus_0.6_nylon-cf-slide_0.3mm.inst.cfg @@ -0,0 +1,17 @@ +[general] +definition = ultimaker_s8 +name = Extra Fast +version = 4 + +[metadata] +material = generic_nylon-cf-slide +quality_type = verydraft +setting_version = 25 +type = quality +variant = CC+ 0.6 +weight = -3 + +[values] +cool_min_layer_time_fan_speed_max = 11 +retraction_prime_speed = 15 + diff --git a/resources/quality/ultimaker_s8/um_s8_cc_plus_0.6_petcf_0.2mm.inst.cfg b/resources/quality/ultimaker_s8/um_s8_cc_plus_0.6_petcf_0.2mm.inst.cfg new file mode 100644 index 0000000000..52dc6f1a5e --- /dev/null +++ b/resources/quality/ultimaker_s8/um_s8_cc_plus_0.6_petcf_0.2mm.inst.cfg @@ -0,0 +1,18 @@ +[general] +definition = ultimaker_s8 +name = Fast +version = 4 + +[metadata] +material = generic_petcf +quality_type = draft +setting_version = 25 +type = quality +variant = CC+ 0.6 +weight = -2 + +[values] +bridge_skin_material_flow = 200 +bridge_wall_material_flow = 200 +support_interface_enable = False + diff --git a/resources/quality/ultimaker_s8/um_s8_cc_plus_0.6_petcf_0.3mm.inst.cfg b/resources/quality/ultimaker_s8/um_s8_cc_plus_0.6_petcf_0.3mm.inst.cfg new file mode 100644 index 0000000000..cd42a93fec --- /dev/null +++ b/resources/quality/ultimaker_s8/um_s8_cc_plus_0.6_petcf_0.3mm.inst.cfg @@ -0,0 +1,20 @@ +[general] +definition = ultimaker_s8 +name = Extra Fast +version = 4 + +[metadata] +material = generic_petcf +quality_type = verydraft +setting_version = 25 +type = quality +variant = CC+ 0.6 +weight = -3 + +[values] +bridge_skin_material_flow = 200 +bridge_wall_material_flow = 200 +material_print_temperature = =default_material_print_temperature + 10 +support_interface_enable = False +wall_line_width_0 = =line_width * (1 + magic_spiralize * 0.25) + diff --git a/resources/variants/ultimaker_s6_aa_plus04.inst.cfg b/resources/variants/ultimaker_s6_aa_plus04.inst.cfg new file mode 100644 index 0000000000..5aba46a964 --- /dev/null +++ b/resources/variants/ultimaker_s6_aa_plus04.inst.cfg @@ -0,0 +1,17 @@ +[general] +definition = ultimaker_s6 +name = AA+ 0.4 +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_cool_down_speed = 0.9 +machine_nozzle_id = AA+ 0.4 +machine_nozzle_size = 0.4 +machine_nozzle_tip_outer_diameter = 1.2 +retraction_prime_speed = =retraction_speed + diff --git a/resources/variants/ultimaker_s6_aa_plus06.inst.cfg b/resources/variants/ultimaker_s6_aa_plus06.inst.cfg new file mode 100644 index 0000000000..95401be2c3 --- /dev/null +++ b/resources/variants/ultimaker_s6_aa_plus06.inst.cfg @@ -0,0 +1,17 @@ +[general] +definition = ultimaker_s6 +name = AA+ 0.6 +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_cool_down_speed = 0.9 +machine_nozzle_id = AA+ 0.6 +machine_nozzle_size = 0.6 +machine_nozzle_tip_outer_diameter = 1.2 +retraction_prime_speed = =retraction_speed + diff --git a/resources/variants/ultimaker_s6_bb04.inst.cfg b/resources/variants/ultimaker_s6_bb04.inst.cfg new file mode 100644 index 0000000000..756d6fd1d4 --- /dev/null +++ b/resources/variants/ultimaker_s6_bb04.inst.cfg @@ -0,0 +1,19 @@ +[general] +definition = ultimaker_s6 +name = BB 0.4 +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_heat_up_speed = 1.5 +machine_nozzle_id = BB 0.4 +machine_nozzle_tip_outer_diameter = 1.0 +retraction_amount = 4.5 +support_bottom_height = =layer_height * 2 +support_interface_enable = True +switch_extruder_retraction_amount = 12 + diff --git a/resources/variants/ultimaker_s6_cc_plus04.inst.cfg b/resources/variants/ultimaker_s6_cc_plus04.inst.cfg new file mode 100644 index 0000000000..61206eb39c --- /dev/null +++ b/resources/variants/ultimaker_s6_cc_plus04.inst.cfg @@ -0,0 +1,17 @@ +[general] +definition = ultimaker_s6 +name = CC+ 0.4 +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_cool_down_speed = 0.9 +machine_nozzle_id = CC+ 0.4 +machine_nozzle_size = 0.4 +machine_nozzle_tip_outer_diameter = 1.2 +retraction_prime_speed = =retraction_speed + diff --git a/resources/variants/ultimaker_s6_cc_plus06.inst.cfg b/resources/variants/ultimaker_s6_cc_plus06.inst.cfg new file mode 100644 index 0000000000..93564bada0 --- /dev/null +++ b/resources/variants/ultimaker_s6_cc_plus06.inst.cfg @@ -0,0 +1,17 @@ +[general] +definition = ultimaker_s6 +name = CC+ 0.6 +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_cool_down_speed = 0.9 +machine_nozzle_id = CC+ 0.6 +machine_nozzle_size = 0.6 +machine_nozzle_tip_outer_diameter = 1.2 +retraction_prime_speed = =retraction_speed + diff --git a/resources/variants/ultimaker_s6_dd04.inst.cfg b/resources/variants/ultimaker_s6_dd04.inst.cfg new file mode 100644 index 0000000000..3125db405e --- /dev/null +++ b/resources/variants/ultimaker_s6_dd04.inst.cfg @@ -0,0 +1,17 @@ +[general] +definition = ultimaker_s6 +name = DD 0.4 +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_cool_down_speed = 0.9 +machine_nozzle_id = DD 0.4 +machine_nozzle_size = 0.4 +machine_nozzle_tip_outer_diameter = 1.2 +retraction_prime_speed = =retraction_speed + diff --git a/resources/variants/ultimaker_s8_aa_plus06.inst.cfg b/resources/variants/ultimaker_s8_aa_plus06.inst.cfg new file mode 100644 index 0000000000..1eabef191c --- /dev/null +++ b/resources/variants/ultimaker_s8_aa_plus06.inst.cfg @@ -0,0 +1,17 @@ +[general] +definition = ultimaker_s8 +name = AA+ 0.6 +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_cool_down_speed = 0.9 +machine_nozzle_id = AA+ 0.6 +machine_nozzle_size = 0.6 +machine_nozzle_tip_outer_diameter = 1.2 +retraction_prime_speed = =retraction_speed + diff --git a/resources/variants/ultimaker_s8_cc_plus06.inst.cfg b/resources/variants/ultimaker_s8_cc_plus06.inst.cfg new file mode 100644 index 0000000000..2a1c43873f --- /dev/null +++ b/resources/variants/ultimaker_s8_cc_plus06.inst.cfg @@ -0,0 +1,17 @@ +[general] +definition = ultimaker_s8 +name = CC+ 0.6 +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 25 +type = variant + +[values] +machine_nozzle_cool_down_speed = 0.9 +machine_nozzle_id = CC+ 0.6 +machine_nozzle_size = 0.6 +machine_nozzle_tip_outer_diameter = 1.2 +retraction_prime_speed = =retraction_speed +