mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-07 06:57:28 -06:00
Merge branch '4.0'
This commit is contained in:
commit
c6da824203
72 changed files with 1910 additions and 555 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -42,7 +42,6 @@ plugins/cura-siemensnx-plugin
|
|||
plugins/CuraBlenderPlugin
|
||||
plugins/CuraCloudPlugin
|
||||
plugins/CuraDrivePlugin
|
||||
plugins/CuraDrive
|
||||
plugins/CuraLiveScriptingPlugin
|
||||
plugins/CuraOpenSCADPlugin
|
||||
plugins/CuraPrintProfileCreator
|
||||
|
|
|
@ -6,6 +6,7 @@ from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty
|
|||
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Message import Message
|
||||
from cura import UltimakerCloudAuthentication
|
||||
|
||||
from cura.OAuth2.AuthorizationService import AuthorizationService
|
||||
from cura.OAuth2.Models import OAuth2Settings
|
||||
|
@ -37,15 +38,16 @@ class Account(QObject):
|
|||
self._logged_in = False
|
||||
|
||||
self._callback_port = 32118
|
||||
self._oauth_root = "https://account.ultimaker.com"
|
||||
self._cloud_api_root = "https://api.ultimaker.com"
|
||||
self._oauth_root = UltimakerCloudAuthentication.CuraCloudAccountAPIRoot
|
||||
|
||||
self._oauth_settings = OAuth2Settings(
|
||||
OAUTH_SERVER_URL= self._oauth_root,
|
||||
CALLBACK_PORT=self._callback_port,
|
||||
CALLBACK_URL="http://localhost:{}/callback".format(self._callback_port),
|
||||
CLIENT_ID="um----------------------------ultimaker_cura",
|
||||
CLIENT_SCOPES="account.user.read drive.backup.read drive.backup.write packages.download packages.rating.read packages.rating.write",
|
||||
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 "
|
||||
"cura.printjob.read cura.printjob.write cura.mesh.read cura.mesh.write",
|
||||
AUTH_DATA_PREFERENCE_KEY="general/ultimaker_auth_data",
|
||||
AUTH_SUCCESS_REDIRECT="{}/app/auth-success".format(self._oauth_root),
|
||||
AUTH_FAILED_REDIRECT="{}/app/auth-error".format(self._oauth_root)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from typing import Tuple, Optional, TYPE_CHECKING
|
||||
from typing import Tuple, Optional, TYPE_CHECKING, Dict, Any
|
||||
|
||||
from cura.Backups.BackupsManager import BackupsManager
|
||||
|
||||
|
@ -24,12 +24,12 @@ class Backups:
|
|||
## 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.
|
||||
def createBackup(self) -> Tuple[Optional[bytes], Optional[dict]]:
|
||||
def createBackup(self) -> Tuple[Optional[bytes], Optional[Dict[str, Any]]]:
|
||||
return self.manager.createBackup()
|
||||
|
||||
## 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.
|
||||
def restoreBackup(self, zip_file: bytes, meta_data: dict) -> None:
|
||||
def restoreBackup(self, zip_file: bytes, meta_data: Dict[str, Any]) -> None:
|
||||
return self.manager.restoreBackup(zip_file, meta_data)
|
||||
|
|
36
cura/ApplicationMetadata.py
Normal file
36
cura/ApplicationMetadata.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
# ---------
|
||||
# Genearl constants used in Cura
|
||||
# ---------
|
||||
DEFAULT_CURA_DISPLAY_NAME = "Ultimaker Cura"
|
||||
DEFAULT_CURA_VERSION = "master"
|
||||
DEFAULT_CURA_BUILD_TYPE = ""
|
||||
DEFAULT_CURA_DEBUG_MODE = False
|
||||
DEFAULT_CURA_SDK_VERSION = "6.0.0"
|
||||
|
||||
try:
|
||||
from cura.CuraVersion import CuraAppDisplayName # type: ignore
|
||||
except ImportError:
|
||||
CuraAppDisplayName = DEFAULT_CURA_DISPLAY_NAME
|
||||
|
||||
try:
|
||||
from cura.CuraVersion import CuraVersion # type: ignore
|
||||
except ImportError:
|
||||
CuraVersion = DEFAULT_CURA_VERSION # [CodeStyle: Reflecting imported value]
|
||||
|
||||
try:
|
||||
from cura.CuraVersion import CuraBuildType # type: ignore
|
||||
except ImportError:
|
||||
CuraBuildType = DEFAULT_CURA_BUILD_TYPE
|
||||
|
||||
try:
|
||||
from cura.CuraVersion import CuraDebugMode # type: ignore
|
||||
except ImportError:
|
||||
CuraDebugMode = DEFAULT_CURA_DEBUG_MODE
|
||||
|
||||
try:
|
||||
from cura.CuraVersion import CuraSDKVersion # type: ignore
|
||||
except ImportError:
|
||||
CuraSDKVersion = DEFAULT_CURA_SDK_VERSION
|
|
@ -117,6 +117,8 @@ from cura.ObjectsModel import ObjectsModel
|
|||
from cura.PrinterOutputDevice import PrinterOutputDevice
|
||||
from cura.PrinterOutput.NetworkMJPGImage import NetworkMJPGImage
|
||||
|
||||
from cura import ApplicationMetadata
|
||||
|
||||
from UM.FlameProfiler import pyqtSlot
|
||||
from UM.Decorators import override
|
||||
|
||||
|
@ -164,11 +166,11 @@ class CuraApplication(QtApplication):
|
|||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(name = "cura",
|
||||
app_display_name = CuraAppDisplayName,
|
||||
version = CuraVersion,
|
||||
api_version = CuraSDKVersion,
|
||||
buildtype = CuraBuildType,
|
||||
is_debug_mode = CuraDebugMode,
|
||||
app_display_name = ApplicationMetadata.CuraAppDisplayName,
|
||||
version = ApplicationMetadata.CuraVersion,
|
||||
api_version = ApplicationMetadata.CuraSDKVersion,
|
||||
buildtype = ApplicationMetadata.CuraBuildType,
|
||||
is_debug_mode = ApplicationMetadata.CuraDebugMode,
|
||||
tray_icon_name = "cura-icon-32.png",
|
||||
**kwargs)
|
||||
|
||||
|
@ -500,7 +502,7 @@ class CuraApplication(QtApplication):
|
|||
preferences.addPreference("cura/choice_on_profile_override", "always_ask")
|
||||
preferences.addPreference("cura/choice_on_open_project", "always_ask")
|
||||
preferences.addPreference("cura/use_multi_build_plate", False)
|
||||
preferences.addPreference("view/settings_list_height", 600)
|
||||
preferences.addPreference("view/settings_list_height", 400)
|
||||
preferences.addPreference("view/settings_visible", False)
|
||||
preferences.addPreference("cura/currency", "€")
|
||||
preferences.addPreference("cura/material_settings", "{}")
|
||||
|
@ -955,7 +957,7 @@ class CuraApplication(QtApplication):
|
|||
engine.rootContext().setContextProperty("CuraApplication", self)
|
||||
engine.rootContext().setContextProperty("PrintInformation", self._print_information)
|
||||
engine.rootContext().setContextProperty("CuraActions", self._cura_actions)
|
||||
engine.rootContext().setContextProperty("CuraSDKVersion", CuraSDKVersion)
|
||||
engine.rootContext().setContextProperty("CuraSDKVersion", ApplicationMetadata.CuraSDKVersion)
|
||||
|
||||
qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type")
|
||||
|
||||
|
|
|
@ -8,3 +8,4 @@ CuraDebugMode = True if "@_cura_debugmode@" == "ON" else False
|
|||
CuraSDKVersion = "@CURA_SDK_VERSION@"
|
||||
CuraCloudAPIRoot = "@CURA_CLOUD_API_ROOT@"
|
||||
CuraCloudAPIVersion = "@CURA_CLOUD_API_VERSION@"
|
||||
CuraCloudAccountAPIRoot = "@CURA_CLOUD_ACCOUNT_API_ROOT@"
|
||||
|
|
|
@ -302,6 +302,10 @@ class MaterialManager(QObject):
|
|||
def getMaterialGroupListByGUID(self, guid: str) -> Optional[List[MaterialGroup]]:
|
||||
return self._guid_material_groups_map.get(guid)
|
||||
|
||||
# Returns a dict of all material groups organized by root_material_id.
|
||||
def getAllMaterialGroups(self) -> Dict[str, "MaterialGroup"]:
|
||||
return self._material_group_map
|
||||
|
||||
#
|
||||
# Return a dict with all root material IDs (k) and ContainerNodes (v) that's suitable for the given setup.
|
||||
#
|
||||
|
@ -679,7 +683,11 @@ class MaterialManager(QObject):
|
|||
|
||||
@pyqtSlot(str)
|
||||
def removeFavorite(self, root_material_id: str) -> None:
|
||||
try:
|
||||
self._favorites.remove(root_material_id)
|
||||
except KeyError:
|
||||
Logger.log("w", "Could not delete material %s from favorites as it was already deleted", root_material_id)
|
||||
return
|
||||
self.materialsUpdated.emit()
|
||||
|
||||
# Ensure all settings are saved.
|
||||
|
|
28
cura/UltimakerCloudAuthentication.py
Normal file
28
cura/UltimakerCloudAuthentication.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
# ---------
|
||||
# Constants used for the Cloud API
|
||||
# ---------
|
||||
DEFAULT_CLOUD_API_ROOT = "https://api.ultimaker.com" # type: str
|
||||
DEFAULT_CLOUD_API_VERSION = 1 # type: int
|
||||
DEFAULT_CLOUD_ACCOUNT_API_ROOT = "https://account.ultimaker.com" # type: str
|
||||
|
||||
try:
|
||||
from cura.CuraVersion import CuraCloudAPIRoot # type: ignore
|
||||
if CuraCloudAPIRoot == "":
|
||||
CuraCloudAPIRoot = DEFAULT_CLOUD_API_ROOT
|
||||
except ImportError:
|
||||
CuraCloudAPIRoot = DEFAULT_CLOUD_API_ROOT
|
||||
|
||||
try:
|
||||
from cura.CuraVersion import CuraCloudAPIVersion # type: ignore
|
||||
except ImportError:
|
||||
CuraCloudAPIVersion = DEFAULT_CLOUD_API_VERSION
|
||||
|
||||
try:
|
||||
from cura.CuraVersion import CuraCloudAccountAPIRoot # type: ignore
|
||||
if CuraCloudAccountAPIRoot == "":
|
||||
CuraCloudAccountAPIRoot = DEFAULT_CLOUD_ACCOUNT_API_ROOT
|
||||
except ImportError:
|
||||
CuraCloudAccountAPIRoot = DEFAULT_CLOUD_ACCOUNT_API_ROOT
|
12
plugins/CuraDrive/__init__.py
Normal file
12
plugins/CuraDrive/__init__.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from .src.DrivePluginExtension import DrivePluginExtension
|
||||
|
||||
|
||||
def getMetaData():
|
||||
return {}
|
||||
|
||||
|
||||
def register(app):
|
||||
return {"extension": DrivePluginExtension()}
|
8
plugins/CuraDrive/plugin.json
Normal file
8
plugins/CuraDrive/plugin.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "Cura Backups",
|
||||
"author": "Ultimaker B.V.",
|
||||
"description": "Backup and restore your configuration.",
|
||||
"version": "1.2.0",
|
||||
"api": 6,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
168
plugins/CuraDrive/src/DriveApiService.py
Normal file
168
plugins/CuraDrive/src/DriveApiService.py
Normal file
|
@ -0,0 +1,168 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
from datetime import datetime
|
||||
from tempfile import NamedTemporaryFile
|
||||
from typing import Any, Optional, List, Dict
|
||||
|
||||
import requests
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.Message import Message
|
||||
from UM.Signal import Signal, signalemitter
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
from .UploadBackupJob import UploadBackupJob
|
||||
from .Settings import Settings
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
## The DriveApiService is responsible for interacting with the CuraDrive API and Cura's backup handling.
|
||||
@signalemitter
|
||||
class DriveApiService:
|
||||
BACKUP_URL = "{}/backups".format(Settings.DRIVE_API_URL)
|
||||
|
||||
# Emit signal when restoring backup started or finished.
|
||||
restoringStateChanged = Signal()
|
||||
|
||||
# Emit signal when creating backup started or finished.
|
||||
creatingStateChanged = Signal()
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._cura_api = CuraApplication.getInstance().getCuraAPI()
|
||||
|
||||
def getBackups(self) -> List[Dict[str, Any]]:
|
||||
access_token = self._cura_api.account.accessToken
|
||||
if not access_token:
|
||||
Logger.log("w", "Could not get access token.")
|
||||
return []
|
||||
|
||||
backup_list_request = requests.get(self.BACKUP_URL, headers = {
|
||||
"Authorization": "Bearer {}".format(access_token)
|
||||
})
|
||||
|
||||
# HTTP status 300s mean redirection. 400s and 500s are errors.
|
||||
# Technically 300s are not errors, but the use case here relies on "requests" to handle redirects automatically.
|
||||
if backup_list_request.status_code >= 300:
|
||||
Logger.log("w", "Could not get backups list from remote: %s", backup_list_request.text)
|
||||
Message(catalog.i18nc("@info:backup_status", "There was an error listing your backups."), title = catalog.i18nc("@info:title", "Backup")).show()
|
||||
return []
|
||||
return backup_list_request.json()["data"]
|
||||
|
||||
def createBackup(self) -> None:
|
||||
self.creatingStateChanged.emit(is_creating = True)
|
||||
|
||||
# Create the backup.
|
||||
backup_zip_file, backup_meta_data = self._cura_api.backups.createBackup()
|
||||
if not backup_zip_file or not backup_meta_data:
|
||||
self.creatingStateChanged.emit(is_creating = False, error_message ="Could not create backup.")
|
||||
return
|
||||
|
||||
# Create an upload entry for the backup.
|
||||
timestamp = datetime.now().isoformat()
|
||||
backup_meta_data["description"] = "{}.backup.{}.cura.zip".format(timestamp, backup_meta_data["cura_release"])
|
||||
backup_upload_url = self._requestBackupUpload(backup_meta_data, len(backup_zip_file))
|
||||
if not backup_upload_url:
|
||||
self.creatingStateChanged.emit(is_creating = False, error_message ="Could not upload backup.")
|
||||
return
|
||||
|
||||
# Upload the backup to storage.
|
||||
upload_backup_job = UploadBackupJob(backup_upload_url, backup_zip_file)
|
||||
upload_backup_job.finished.connect(self._onUploadFinished)
|
||||
upload_backup_job.start()
|
||||
|
||||
def _onUploadFinished(self, job: "UploadBackupJob") -> None:
|
||||
if job.backup_upload_error_message != "":
|
||||
# If the job contains an error message we pass it along so the UI can display it.
|
||||
self.creatingStateChanged.emit(is_creating = False, error_message = job.backup_upload_error_message)
|
||||
else:
|
||||
self.creatingStateChanged.emit(is_creating = False)
|
||||
|
||||
def restoreBackup(self, backup: Dict[str, Any]) -> None:
|
||||
self.restoringStateChanged.emit(is_restoring = True)
|
||||
download_url = backup.get("download_url")
|
||||
if not download_url:
|
||||
# If there is no download URL, we can't restore the backup.
|
||||
return self._emitRestoreError()
|
||||
|
||||
download_package = requests.get(download_url, stream = True)
|
||||
if download_package.status_code >= 300:
|
||||
# Something went wrong when attempting to download the backup.
|
||||
Logger.log("w", "Could not download backup from url %s: %s", download_url, download_package.text)
|
||||
return self._emitRestoreError()
|
||||
|
||||
# We store the file in a temporary path fist to ensure integrity.
|
||||
temporary_backup_file = NamedTemporaryFile(delete = False)
|
||||
with open(temporary_backup_file.name, "wb") as write_backup:
|
||||
for chunk in download_package:
|
||||
write_backup.write(chunk)
|
||||
|
||||
if not self._verifyMd5Hash(temporary_backup_file.name, 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.")
|
||||
return self._emitRestoreError()
|
||||
|
||||
# Tell Cura to place the backup back in the user data folder.
|
||||
with open(temporary_backup_file.name, "rb") as read_backup:
|
||||
self._cura_api.backups.restoreBackup(read_backup.read(), backup.get("metadata", {}))
|
||||
self.restoringStateChanged.emit(is_restoring = False)
|
||||
|
||||
def _emitRestoreError(self) -> None:
|
||||
self.restoringStateChanged.emit(is_restoring = False,
|
||||
error_message = catalog.i18nc("@info:backup_status",
|
||||
"There was an error trying to restore your backup."))
|
||||
|
||||
# Verify the MD5 hash of a file.
|
||||
# \param file_path Full path to the file.
|
||||
# \param known_hash The known MD5 hash of the file.
|
||||
# \return: Success or not.
|
||||
@staticmethod
|
||||
def _verifyMd5Hash(file_path: str, known_hash: str) -> bool:
|
||||
with open(file_path, "rb") as read_backup:
|
||||
local_md5_hash = base64.b64encode(hashlib.md5(read_backup.read()).digest(), altchars = b"_-").decode("utf-8")
|
||||
return known_hash == local_md5_hash
|
||||
|
||||
def deleteBackup(self, backup_id: str) -> bool:
|
||||
access_token = self._cura_api.account.accessToken
|
||||
if not access_token:
|
||||
Logger.log("w", "Could not get access token.")
|
||||
return False
|
||||
|
||||
delete_backup = requests.delete("{}/{}".format(self.BACKUP_URL, backup_id), headers = {
|
||||
"Authorization": "Bearer {}".format(access_token)
|
||||
})
|
||||
if delete_backup.status_code >= 300:
|
||||
Logger.log("w", "Could not delete backup: %s", delete_backup.text)
|
||||
return False
|
||||
return True
|
||||
|
||||
# Request a backup upload slot from the API.
|
||||
# \param backup_metadata: A dict containing some meta data about the backup.
|
||||
# \param backup_size The size of the backup file in bytes.
|
||||
# \return: The upload URL for the actual backup file if successful, otherwise None.
|
||||
def _requestBackupUpload(self, backup_metadata: Dict[str, Any], backup_size: int) -> Optional[str]:
|
||||
access_token = self._cura_api.account.accessToken
|
||||
if not access_token:
|
||||
Logger.log("w", "Could not get access token.")
|
||||
return None
|
||||
|
||||
backup_upload_request = requests.put(self.BACKUP_URL, json = {
|
||||
"data": {
|
||||
"backup_size": backup_size,
|
||||
"metadata": backup_metadata
|
||||
}
|
||||
}, headers = {
|
||||
"Authorization": "Bearer {}".format(access_token)
|
||||
})
|
||||
|
||||
# Any status code of 300 or above indicates an error.
|
||||
if backup_upload_request.status_code >= 300:
|
||||
Logger.log("w", "Could not request backup upload: %s", backup_upload_request.text)
|
||||
return None
|
||||
|
||||
return backup_upload_request.json()["data"]["upload_url"]
|
162
plugins/CuraDrive/src/DrivePluginExtension.py
Normal file
162
plugins/CuraDrive/src/DrivePluginExtension.py
Normal file
|
@ -0,0 +1,162 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import Optional, List, Dict, Any
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
|
||||
|
||||
from UM.Extension import Extension
|
||||
from UM.Logger import Logger
|
||||
from UM.Message import Message
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
from .Settings import Settings
|
||||
from .DriveApiService import DriveApiService
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
# The DivePluginExtension provides functionality to backup and restore your Cura configuration to Ultimaker's cloud.
|
||||
class DrivePluginExtension(QObject, Extension):
|
||||
|
||||
# Signal emitted when the list of backups changed.
|
||||
backupsChanged = pyqtSignal()
|
||||
|
||||
# Signal emitted when restoring has started. Needed to prevent parallel restoring.
|
||||
restoringStateChanged = pyqtSignal()
|
||||
|
||||
# Signal emitted when creating has started. Needed to prevent parallel creation of backups.
|
||||
creatingStateChanged = pyqtSignal()
|
||||
|
||||
# Signal emitted when preferences changed (like auto-backup).
|
||||
preferencesChanged = pyqtSignal()
|
||||
|
||||
DATE_FORMAT = "%d/%m/%Y %H:%M:%S"
|
||||
|
||||
def __init__(self) -> None:
|
||||
QObject.__init__(self, None)
|
||||
Extension.__init__(self)
|
||||
|
||||
# Local data caching for the UI.
|
||||
self._drive_window = None # type: Optional[QObject]
|
||||
self._backups = [] # type: List[Dict[str, Any]]
|
||||
self._is_restoring_backup = False
|
||||
self._is_creating_backup = False
|
||||
|
||||
# Initialize services.
|
||||
preferences = CuraApplication.getInstance().getPreferences()
|
||||
self._drive_api_service = DriveApiService()
|
||||
|
||||
# Attach signals.
|
||||
CuraApplication.getInstance().getCuraAPI().account.loginStateChanged.connect(self._onLoginStateChanged)
|
||||
self._drive_api_service.restoringStateChanged.connect(self._onRestoringStateChanged)
|
||||
self._drive_api_service.creatingStateChanged.connect(self._onCreatingStateChanged)
|
||||
|
||||
# Register preferences.
|
||||
preferences.addPreference(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY, False)
|
||||
preferences.addPreference(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY,
|
||||
datetime.now().strftime(self.DATE_FORMAT))
|
||||
|
||||
# Register the menu item
|
||||
self.addMenuItem(catalog.i18nc("@item:inmenu", "Manage backups"), self.showDriveWindow)
|
||||
|
||||
# Make auto-backup on boot if required.
|
||||
CuraApplication.getInstance().engineCreatedSignal.connect(self._autoBackup)
|
||||
|
||||
def showDriveWindow(self) -> None:
|
||||
if not self._drive_window:
|
||||
plugin_dir_path = CuraApplication.getInstance().getPluginRegistry().getPluginPath("CuraDrive")
|
||||
path = os.path.join(plugin_dir_path, "src", "qml", "main.qml")
|
||||
self._drive_window = CuraApplication.getInstance().createQmlComponent(path, {"CuraDrive": self})
|
||||
self.refreshBackups()
|
||||
if self._drive_window:
|
||||
self._drive_window.show()
|
||||
|
||||
def _autoBackup(self) -> None:
|
||||
preferences = CuraApplication.getInstance().getPreferences()
|
||||
if preferences.getValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY) and self._isLastBackupTooLongAgo():
|
||||
self.createBackup()
|
||||
|
||||
def _isLastBackupTooLongAgo(self) -> bool:
|
||||
current_date = datetime.now()
|
||||
last_backup_date = self._getLastBackupDate()
|
||||
date_diff = current_date - last_backup_date
|
||||
return date_diff.days > 1
|
||||
|
||||
def _getLastBackupDate(self) -> "datetime":
|
||||
preferences = CuraApplication.getInstance().getPreferences()
|
||||
last_backup_date = preferences.getValue(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY)
|
||||
return datetime.strptime(last_backup_date, self.DATE_FORMAT)
|
||||
|
||||
def _storeBackupDate(self) -> None:
|
||||
backup_date = datetime.now().strftime(self.DATE_FORMAT)
|
||||
preferences = CuraApplication.getInstance().getPreferences()
|
||||
preferences.setValue(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY, backup_date)
|
||||
|
||||
def _onLoginStateChanged(self, logged_in: bool = False) -> None:
|
||||
if logged_in:
|
||||
self.refreshBackups()
|
||||
|
||||
def _onRestoringStateChanged(self, is_restoring: bool = False, error_message: str = None) -> None:
|
||||
self._is_restoring_backup = is_restoring
|
||||
self.restoringStateChanged.emit()
|
||||
if error_message:
|
||||
Message(error_message, title = catalog.i18nc("@info:title", "Backup")).show()
|
||||
|
||||
def _onCreatingStateChanged(self, is_creating: bool = False, error_message: str = None) -> None:
|
||||
self._is_creating_backup = is_creating
|
||||
self.creatingStateChanged.emit()
|
||||
if error_message:
|
||||
Message(error_message, title = catalog.i18nc("@info:title", "Backup")).show()
|
||||
else:
|
||||
self._storeBackupDate()
|
||||
if not is_creating and not error_message:
|
||||
# We've finished creating a new backup, to the list has to be updated.
|
||||
self.refreshBackups()
|
||||
|
||||
@pyqtSlot(bool, name = "toggleAutoBackup")
|
||||
def toggleAutoBackup(self, enabled: bool) -> None:
|
||||
preferences = CuraApplication.getInstance().getPreferences()
|
||||
preferences.setValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY, enabled)
|
||||
|
||||
@pyqtProperty(bool, notify = preferencesChanged)
|
||||
def autoBackupEnabled(self) -> bool:
|
||||
preferences = CuraApplication.getInstance().getPreferences()
|
||||
return bool(preferences.getValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY))
|
||||
|
||||
@pyqtProperty("QVariantList", notify = backupsChanged)
|
||||
def backups(self) -> List[Dict[str, Any]]:
|
||||
return self._backups
|
||||
|
||||
@pyqtSlot(name = "refreshBackups")
|
||||
def refreshBackups(self) -> None:
|
||||
self._backups = self._drive_api_service.getBackups()
|
||||
self.backupsChanged.emit()
|
||||
|
||||
@pyqtProperty(bool, notify = restoringStateChanged)
|
||||
def isRestoringBackup(self) -> bool:
|
||||
return self._is_restoring_backup
|
||||
|
||||
@pyqtProperty(bool, notify = creatingStateChanged)
|
||||
def isCreatingBackup(self) -> bool:
|
||||
return self._is_creating_backup
|
||||
|
||||
@pyqtSlot(str, name = "restoreBackup")
|
||||
def restoreBackup(self, backup_id: str) -> None:
|
||||
for backup in self._backups:
|
||||
if backup.get("backup_id") == backup_id:
|
||||
self._drive_api_service.restoreBackup(backup)
|
||||
return
|
||||
Logger.log("w", "Unable to find backup with the ID %s", backup_id)
|
||||
|
||||
@pyqtSlot(name = "createBackup")
|
||||
def createBackup(self) -> None:
|
||||
self._drive_api_service.createBackup()
|
||||
|
||||
@pyqtSlot(str, name = "deleteBackup")
|
||||
def deleteBackup(self, backup_id: str) -> None:
|
||||
self._drive_api_service.deleteBackup(backup_id)
|
||||
self.refreshBackups()
|
13
plugins/CuraDrive/src/Settings.py
Normal file
13
plugins/CuraDrive/src/Settings.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from cura import UltimakerCloudAuthentication
|
||||
|
||||
|
||||
class Settings:
|
||||
# Keeps the plugin settings.
|
||||
DRIVE_API_VERSION = 1
|
||||
DRIVE_API_URL = "{}/cura-drive/v{}".format(UltimakerCloudAuthentication.CuraCloudAPIRoot, str(DRIVE_API_VERSION))
|
||||
|
||||
AUTO_BACKUP_ENABLED_PREFERENCE_KEY = "cura_drive/auto_backup_enabled"
|
||||
AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY = "cura_drive/auto_backup_date"
|
41
plugins/CuraDrive/src/UploadBackupJob.py
Normal file
41
plugins/CuraDrive/src/UploadBackupJob.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import requests
|
||||
|
||||
from UM.Job import Job
|
||||
from UM.Logger import Logger
|
||||
from UM.Message import Message
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
class UploadBackupJob(Job):
|
||||
MESSAGE_TITLE = catalog.i18nc("@info:title", "Backups")
|
||||
|
||||
# This job is responsible for uploading the backup file to cloud storage.
|
||||
# As it can take longer than some other tasks, we schedule this using a Cura Job.
|
||||
def __init__(self, signed_upload_url: str, backup_zip: bytes) -> None:
|
||||
super().__init__()
|
||||
self._signed_upload_url = signed_upload_url
|
||||
self._backup_zip = backup_zip
|
||||
self._upload_success = False
|
||||
self.backup_upload_error_message = ""
|
||||
|
||||
def run(self) -> None:
|
||||
upload_message = Message(catalog.i18nc("@info:backup_status", "Uploading your backup..."), title = self.MESSAGE_TITLE, progress = -1)
|
||||
upload_message.show()
|
||||
|
||||
backup_upload = requests.put(self._signed_upload_url, data = self._backup_zip)
|
||||
upload_message.hide()
|
||||
|
||||
if backup_upload.status_code >= 300:
|
||||
self.backup_upload_error_message = backup_upload.text
|
||||
Logger.log("w", "Could not upload backup file: %s", backup_upload.text)
|
||||
Message(catalog.i18nc("@info:backup_status", "There was an error while uploading your backup."), title = self.MESSAGE_TITLE).show()
|
||||
else:
|
||||
self._upload_success = True
|
||||
Message(catalog.i18nc("@info:backup_status", "Your backup has finished uploading."), title = self.MESSAGE_TITLE).show()
|
||||
|
||||
self.finished.emit(self)
|
0
plugins/CuraDrive/src/__init__.py
Normal file
0
plugins/CuraDrive/src/__init__.py
Normal file
39
plugins/CuraDrive/src/qml/components/BackupList.qml
Normal file
39
plugins/CuraDrive/src/qml/components/BackupList.qml
Normal file
|
@ -0,0 +1,39 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
import UM 1.1 as UM
|
||||
|
||||
ScrollView
|
||||
{
|
||||
property alias model: backupList.model
|
||||
width: parent.width
|
||||
clip: true
|
||||
ListView
|
||||
{
|
||||
id: backupList
|
||||
width: parent.width
|
||||
delegate: Item
|
||||
{
|
||||
// Add a margin, otherwise the scrollbar is on top of the right most component
|
||||
width: parent.width - UM.Theme.getSize("default_margin").width
|
||||
height: childrenRect.height
|
||||
|
||||
BackupListItem
|
||||
{
|
||||
id: backupListItem
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: divider
|
||||
color: UM.Theme.getColor("lining")
|
||||
height: UM.Theme.getSize("default_lining").height
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
46
plugins/CuraDrive/src/qml/components/BackupListFooter.qml
Normal file
46
plugins/CuraDrive/src/qml/components/BackupListFooter.qml
Normal file
|
@ -0,0 +1,46 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
import UM 1.3 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
import "../components"
|
||||
|
||||
RowLayout
|
||||
{
|
||||
id: backupListFooter
|
||||
width: parent.width
|
||||
property bool showInfoButton: false
|
||||
|
||||
Cura.PrimaryButton
|
||||
{
|
||||
id: infoButton
|
||||
text: catalog.i18nc("@button", "Want more?")
|
||||
iconSource: UM.Theme.getIcon("info")
|
||||
onClicked: Qt.openUrlExternally("https://goo.gl/forms/QACEP8pP3RV60QYG2")
|
||||
visible: backupListFooter.showInfoButton
|
||||
}
|
||||
|
||||
Cura.PrimaryButton
|
||||
{
|
||||
id: createBackupButton
|
||||
text: catalog.i18nc("@button", "Backup Now")
|
||||
iconSource: UM.Theme.getIcon("plus")
|
||||
enabled: !CuraDrive.isCreatingBackup && !CuraDrive.isRestoringBackup && !backupListFooter.showInfoButton
|
||||
onClicked: CuraDrive.createBackup()
|
||||
busy: CuraDrive.isCreatingBackup
|
||||
}
|
||||
|
||||
Cura.CheckBoxWithTooltip
|
||||
{
|
||||
id: autoBackupEnabled
|
||||
checked: CuraDrive.autoBackupEnabled
|
||||
onClicked: CuraDrive.toggleAutoBackup(autoBackupEnabled.checked)
|
||||
text: catalog.i18nc("@checkbox:description", "Auto Backup")
|
||||
tooltip: catalog.i18nc("@checkbox:description", "Automatically create a backup each day that Cura is started.")
|
||||
}
|
||||
}
|
113
plugins/CuraDrive/src/qml/components/BackupListItem.qml
Normal file
113
plugins/CuraDrive/src/qml/components/BackupListItem.qml
Normal file
|
@ -0,0 +1,113 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Dialogs 1.1
|
||||
|
||||
import UM 1.1 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
Item
|
||||
{
|
||||
id: backupListItem
|
||||
width: parent.width
|
||||
height: showDetails ? dataRow.height + backupDetails.height : dataRow.height
|
||||
property bool showDetails: false
|
||||
|
||||
// Backup details toggle animation.
|
||||
Behavior on height
|
||||
{
|
||||
PropertyAnimation
|
||||
{
|
||||
duration: 70
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout
|
||||
{
|
||||
id: dataRow
|
||||
spacing: UM.Theme.getSize("wide_margin").width
|
||||
width: parent.width
|
||||
height: 50 * screenScaleFactor
|
||||
|
||||
UM.SimpleButton
|
||||
{
|
||||
width: UM.Theme.getSize("section_icon").width
|
||||
height: UM.Theme.getSize("section_icon").height
|
||||
color: UM.Theme.getColor("small_button_text")
|
||||
hoverColor: UM.Theme.getColor("small_button_text_hover")
|
||||
iconSource: UM.Theme.getIcon("info")
|
||||
onClicked: backupListItem.showDetails = !backupListItem.showDetails
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
text: new Date(modelData.generated_time).toLocaleString(UM.Preferences.getValue("general/language"))
|
||||
color: UM.Theme.getColor("text")
|
||||
elide: Text.ElideRight
|
||||
Layout.minimumWidth: 100 * screenScaleFactor
|
||||
Layout.maximumWidth: 500 * screenScaleFactor
|
||||
Layout.fillWidth: true
|
||||
font: UM.Theme.getFont("default")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
text: modelData.metadata.description
|
||||
color: UM.Theme.getColor("text")
|
||||
elide: Text.ElideRight
|
||||
Layout.minimumWidth: 100 * screenScaleFactor
|
||||
Layout.maximumWidth: 500 * screenScaleFactor
|
||||
Layout.fillWidth: true
|
||||
font: UM.Theme.getFont("default")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Cura.SecondaryButton
|
||||
{
|
||||
text: catalog.i18nc("@button", "Restore")
|
||||
enabled: !CuraDrive.isCreatingBackup && !CuraDrive.isRestoringBackup
|
||||
onClicked: confirmRestoreDialog.visible = true
|
||||
}
|
||||
|
||||
UM.SimpleButton
|
||||
{
|
||||
width: UM.Theme.getSize("message_close").width
|
||||
height: UM.Theme.getSize("message_close").height
|
||||
color: UM.Theme.getColor("small_button_text")
|
||||
hoverColor: UM.Theme.getColor("small_button_text_hover")
|
||||
iconSource: UM.Theme.getIcon("cross1")
|
||||
onClicked: confirmDeleteDialog.visible = true
|
||||
}
|
||||
}
|
||||
|
||||
BackupListItemDetails
|
||||
{
|
||||
id: backupDetails
|
||||
backupDetailsData: modelData
|
||||
width: parent.width
|
||||
visible: parent.showDetails
|
||||
anchors.top: dataRow.bottom
|
||||
}
|
||||
|
||||
MessageDialog
|
||||
{
|
||||
id: confirmDeleteDialog
|
||||
title: catalog.i18nc("@dialog:title", "Delete Backup")
|
||||
text: catalog.i18nc("@dialog:info", "Are you sure you want to delete this backup? This cannot be undone.")
|
||||
standardButtons: StandardButton.Yes | StandardButton.No
|
||||
onYes: CuraDrive.deleteBackup(modelData.backup_id)
|
||||
}
|
||||
|
||||
MessageDialog
|
||||
{
|
||||
id: confirmRestoreDialog
|
||||
title: catalog.i18nc("@dialog:title", "Restore Backup")
|
||||
text: catalog.i18nc("@dialog:info", "You will need to restart Cura before your backup is restored. Do you want to close Cura now?")
|
||||
standardButtons: StandardButton.Yes | StandardButton.No
|
||||
onYes: CuraDrive.restoreBackup(modelData.backup_id)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
import UM 1.1 as UM
|
||||
|
||||
ColumnLayout
|
||||
{
|
||||
id: backupDetails
|
||||
width: parent.width
|
||||
spacing: UM.Theme.getSize("default_margin").width
|
||||
property var backupDetailsData
|
||||
|
||||
// Cura version
|
||||
BackupListItemDetailsRow
|
||||
{
|
||||
iconSource: UM.Theme.getIcon("application")
|
||||
label: catalog.i18nc("@backuplist:label", "Cura Version")
|
||||
value: backupDetailsData.metadata.cura_release
|
||||
}
|
||||
|
||||
// Machine count.
|
||||
BackupListItemDetailsRow
|
||||
{
|
||||
iconSource: UM.Theme.getIcon("printer_single")
|
||||
label: catalog.i18nc("@backuplist:label", "Machines")
|
||||
value: backupDetailsData.metadata.machine_count
|
||||
}
|
||||
|
||||
// Material count
|
||||
BackupListItemDetailsRow
|
||||
{
|
||||
iconSource: UM.Theme.getIcon("category_material")
|
||||
label: catalog.i18nc("@backuplist:label", "Materials")
|
||||
value: backupDetailsData.metadata.material_count
|
||||
}
|
||||
|
||||
// Profile count.
|
||||
BackupListItemDetailsRow
|
||||
{
|
||||
iconSource: UM.Theme.getIcon("settings")
|
||||
label: catalog.i18nc("@backuplist:label", "Profiles")
|
||||
value: backupDetailsData.metadata.profile_count
|
||||
}
|
||||
|
||||
// Plugin count.
|
||||
BackupListItemDetailsRow
|
||||
{
|
||||
iconSource: UM.Theme.getIcon("plugin")
|
||||
label: catalog.i18nc("@backuplist:label", "Plugins")
|
||||
value: backupDetailsData.metadata.plugin_count
|
||||
}
|
||||
|
||||
// Spacer.
|
||||
Item
|
||||
{
|
||||
width: parent.width
|
||||
height: UM.Theme.getSize("default_margin").height
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
import UM 1.3 as UM
|
||||
|
||||
RowLayout
|
||||
{
|
||||
id: detailsRow
|
||||
width: parent.width
|
||||
height: 40 * screenScaleFactor
|
||||
|
||||
property alias iconSource: icon.source
|
||||
property alias label: detailName.text
|
||||
property alias value: detailValue.text
|
||||
|
||||
UM.RecolorImage
|
||||
{
|
||||
id: icon
|
||||
width: 18 * screenScaleFactor
|
||||
height: width
|
||||
source: ""
|
||||
color: UM.Theme.getColor("text")
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: detailName
|
||||
color: UM.Theme.getColor("text")
|
||||
elide: Text.ElideRight
|
||||
Layout.minimumWidth: 50 * screenScaleFactor
|
||||
Layout.maximumWidth: 100 * screenScaleFactor
|
||||
Layout.fillWidth: true
|
||||
font: UM.Theme.getFont("default")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: detailValue
|
||||
color: UM.Theme.getColor("text")
|
||||
elide: Text.ElideRight
|
||||
Layout.minimumWidth: 50 * screenScaleFactor
|
||||
Layout.maximumWidth: 100 * screenScaleFactor
|
||||
Layout.fillWidth: true
|
||||
font: UM.Theme.getFont("default")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
BIN
plugins/CuraDrive/src/qml/images/icon.png
Normal file
BIN
plugins/CuraDrive/src/qml/images/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
BIN
plugins/CuraDrive/src/qml/images/loading.gif
Normal file
BIN
plugins/CuraDrive/src/qml/images/loading.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.6 KiB |
44
plugins/CuraDrive/src/qml/main.qml
Normal file
44
plugins/CuraDrive/src/qml/main.qml
Normal file
|
@ -0,0 +1,44 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Window 2.2
|
||||
|
||||
import UM 1.3 as UM
|
||||
import Cura 1.1 as Cura
|
||||
|
||||
import "components"
|
||||
import "pages"
|
||||
|
||||
Window
|
||||
{
|
||||
id: curaDriveDialog
|
||||
minimumWidth: Math.round(UM.Theme.getSize("modal_window_minimum").width)
|
||||
minimumHeight: Math.round(UM.Theme.getSize("modal_window_minimum").height)
|
||||
maximumWidth: Math.round(minimumWidth * 1.2)
|
||||
maximumHeight: Math.round(minimumHeight * 1.2)
|
||||
width: minimumWidth
|
||||
height: minimumHeight
|
||||
color: UM.Theme.getColor("main_background")
|
||||
title: catalog.i18nc("@title:window", "Cura Backups")
|
||||
|
||||
// Globally available.
|
||||
UM.I18nCatalog
|
||||
{
|
||||
id: catalog
|
||||
name: "cura"
|
||||
}
|
||||
|
||||
WelcomePage
|
||||
{
|
||||
id: welcomePage
|
||||
visible: !Cura.API.account.isLoggedIn
|
||||
}
|
||||
|
||||
BackupsPage
|
||||
{
|
||||
id: backupsPage
|
||||
visible: Cura.API.account.isLoggedIn
|
||||
}
|
||||
}
|
75
plugins/CuraDrive/src/qml/pages/BackupsPage.qml
Normal file
75
plugins/CuraDrive/src/qml/pages/BackupsPage.qml
Normal file
|
@ -0,0 +1,75 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
import UM 1.3 as UM
|
||||
import Cura 1.1 as Cura
|
||||
|
||||
import "../components"
|
||||
|
||||
Item
|
||||
{
|
||||
id: backupsPage
|
||||
anchors.fill: parent
|
||||
anchors.margins: UM.Theme.getSize("wide_margin").width
|
||||
|
||||
ColumnLayout
|
||||
{
|
||||
spacing: UM.Theme.getSize("wide_margin").height
|
||||
width: parent.width
|
||||
anchors.fill: parent
|
||||
|
||||
Label
|
||||
{
|
||||
id: backupTitle
|
||||
text: catalog.i18nc("@title", "My Backups")
|
||||
font: UM.Theme.getFont("large")
|
||||
color: UM.Theme.getColor("text")
|
||||
Layout.fillWidth: true
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@empty_state",
|
||||
"You don't have any backups currently. Use the 'Backup Now' button to create one.")
|
||||
width: parent.width
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text")
|
||||
wrapMode: Label.WordWrap
|
||||
visible: backupList.count == 0
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
BackupList
|
||||
{
|
||||
id: backupList
|
||||
model: CuraDrive.backups
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@backup_limit_info",
|
||||
"During the preview phase, you'll be limited to 5 visible backups. Remove a backup to see older ones.")
|
||||
width: parent.width
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text")
|
||||
wrapMode: Label.WordWrap
|
||||
visible: backupList.count > 4
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
BackupListFooter
|
||||
{
|
||||
id: backupListFooter
|
||||
showInfoButton: backupList.count > 4
|
||||
}
|
||||
}
|
||||
}
|
56
plugins/CuraDrive/src/qml/pages/WelcomePage.qml
Normal file
56
plugins/CuraDrive/src/qml/pages/WelcomePage.qml
Normal file
|
@ -0,0 +1,56 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Window 2.2
|
||||
|
||||
import UM 1.3 as UM
|
||||
import Cura 1.1 as Cura
|
||||
|
||||
import "../components"
|
||||
|
||||
|
||||
Column
|
||||
{
|
||||
id: welcomePage
|
||||
spacing: UM.Theme.getSize("wide_margin").height
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
anchors.centerIn: parent
|
||||
|
||||
Image
|
||||
{
|
||||
id: profileImage
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: "../images/icon.png"
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: Math.round(parent.width / 4)
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: welcomeTextLabel
|
||||
text: catalog.i18nc("@description", "Backup and synchronize your Cura settings.")
|
||||
width: Math.round(parent.width / 2)
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text")
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
wrapMode: Label.WordWrap
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Cura.PrimaryButton
|
||||
{
|
||||
id: loginButton
|
||||
width: UM.Theme.getSize("account_button").width
|
||||
height: UM.Theme.getSize("account_button").height
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: catalog.i18nc("@button", "Sign in")
|
||||
onClicked: Cura.API.account.login()
|
||||
fixedWidthMode: true
|
||||
}
|
||||
}
|
||||
|
|
@ -833,7 +833,10 @@ class CuraEngineBackend(QObject, Backend):
|
|||
self._onChanged()
|
||||
|
||||
def _onProcessLayersFinished(self, job: ProcessSlicedLayersJob) -> None:
|
||||
if job.getBuildPlate() in self._stored_optimized_layer_data:
|
||||
del self._stored_optimized_layer_data[job.getBuildPlate()]
|
||||
else:
|
||||
Logger.log("w", "The optimized layer data was already deleted for buildplate %s", job.getBuildPlate())
|
||||
self._process_layers_job = None
|
||||
Logger.log("d", "See if there is more to slice(2)...")
|
||||
self._invokeSlice()
|
||||
|
|
|
@ -57,7 +57,7 @@ class FirmwareUpdaterMachineAction(MachineAction):
|
|||
outputDeviceCanUpdateFirmwareChanged = pyqtSignal()
|
||||
@pyqtProperty(QObject, notify = outputDeviceCanUpdateFirmwareChanged)
|
||||
def firmwareUpdater(self) -> Optional["FirmwareUpdater"]:
|
||||
if self._active_output_device and self._active_output_device.activePrinter.getController().can_update_firmware:
|
||||
if self._active_output_device and self._active_output_device.activePrinter and self._active_output_device.activePrinter.getController().can_update_firmware:
|
||||
self._active_firmware_updater = self._active_output_device.getFirmwareUpdater()
|
||||
return self._active_firmware_updater
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright (c) 2017 Ultimaker B.V.
|
||||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 1.4
|
||||
|
@ -7,11 +8,9 @@ import UM 1.3 as UM
|
|||
import Cura 1.0 as Cura
|
||||
|
||||
|
||||
Item
|
||||
// We show a nice overlay on the 3D viewer when the current output device has no monitor view
|
||||
Rectangle
|
||||
{
|
||||
// We show a nice overlay on the 3D viewer when the current output device has no monitor view
|
||||
Rectangle
|
||||
{
|
||||
id: viewportOverlay
|
||||
|
||||
color: UM.Theme.getColor("viewport_overlay")
|
||||
|
@ -30,8 +29,6 @@ Item
|
|||
{
|
||||
anchors.fill: parent
|
||||
}
|
||||
}
|
||||
|
||||
Loader
|
||||
{
|
||||
id: monitorViewComponent
|
||||
|
|
|
@ -488,7 +488,7 @@ UM.Dialog
|
|||
{
|
||||
objectName: "postProcessingSaveAreaButton"
|
||||
visible: activeScriptsList.count > 0
|
||||
height: UM.Theme.getSize("save_button_save_to_button").height
|
||||
height: UM.Theme.getSize("action_button").height
|
||||
width: height
|
||||
tooltip: catalog.i18nc("@info:tooltip", "Change active post-processing scripts")
|
||||
onClicked: dialog.show()
|
||||
|
|
|
@ -38,7 +38,7 @@ Window
|
|||
{
|
||||
id: mainView
|
||||
width: parent.width
|
||||
z: -1
|
||||
z: parent.z - 1
|
||||
anchors
|
||||
{
|
||||
top: header.bottom
|
||||
|
|
|
@ -91,5 +91,10 @@ Column
|
|||
target: toolbox
|
||||
onInstallChanged: installed = toolbox.isInstalled(model.id)
|
||||
onMetadataChanged: canUpdate = toolbox.canUpdate(model.id)
|
||||
onFilterChanged:
|
||||
{
|
||||
installed = toolbox.isInstalled(model.id)
|
||||
canUpdate = toolbox.canUpdate(model.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ Item
|
|||
CheckBox
|
||||
{
|
||||
id: disableButton
|
||||
anchors.verticalCenter: pluginInfo.verticalCenter
|
||||
checked: isEnabled
|
||||
visible: model.type == "plugin"
|
||||
width: visible ? UM.Theme.getSize("checkbox").width : 0
|
||||
|
|
|
@ -16,7 +16,8 @@ from UM.Extension import Extension
|
|||
from UM.i18n import i18nCatalog
|
||||
from UM.Version import Version
|
||||
|
||||
import cura
|
||||
from cura import ApplicationMetadata
|
||||
from cura import UltimakerCloudAuthentication
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
from .AuthorsModel import AuthorsModel
|
||||
|
@ -30,17 +31,14 @@ i18n_catalog = i18nCatalog("cura")
|
|||
|
||||
## The Toolbox class is responsible of communicating with the server through the API
|
||||
class Toolbox(QObject, Extension):
|
||||
DEFAULT_CLOUD_API_ROOT = "https://api.ultimaker.com" # type: str
|
||||
DEFAULT_CLOUD_API_VERSION = 1 # type: int
|
||||
|
||||
def __init__(self, application: CuraApplication) -> None:
|
||||
super().__init__()
|
||||
|
||||
self._application = application # type: CuraApplication
|
||||
|
||||
self._sdk_version = None # type: Optional[Union[str, int]]
|
||||
self._cloud_api_version = None # type: Optional[int]
|
||||
self._cloud_api_root = None # type: Optional[str]
|
||||
self._sdk_version = ApplicationMetadata.CuraSDKVersion # type: Union[str, int]
|
||||
self._cloud_api_version = UltimakerCloudAuthentication.CuraCloudAPIVersion # type: int
|
||||
self._cloud_api_root = UltimakerCloudAuthentication.CuraCloudAPIRoot # type: str
|
||||
self._api_url = None # type: Optional[str]
|
||||
|
||||
# Network:
|
||||
|
@ -182,9 +180,6 @@ class Toolbox(QObject, Extension):
|
|||
def _onAppInitialized(self) -> None:
|
||||
self._plugin_registry = self._application.getPluginRegistry()
|
||||
self._package_manager = self._application.getPackageManager()
|
||||
self._sdk_version = self._getSDKVersion()
|
||||
self._cloud_api_version = self._getCloudAPIVersion()
|
||||
self._cloud_api_root = self._getCloudAPIRoot()
|
||||
self._api_url = "{cloud_api_root}/cura-packages/v{cloud_api_version}/cura/v{sdk_version}".format(
|
||||
cloud_api_root = self._cloud_api_root,
|
||||
cloud_api_version = self._cloud_api_version,
|
||||
|
@ -195,36 +190,6 @@ class Toolbox(QObject, Extension):
|
|||
"packages": QUrl("{base_url}/packages".format(base_url = self._api_url))
|
||||
}
|
||||
|
||||
# Get the API root for the packages API depending on Cura version settings.
|
||||
def _getCloudAPIRoot(self) -> str:
|
||||
if not hasattr(cura, "CuraVersion"):
|
||||
return self.DEFAULT_CLOUD_API_ROOT
|
||||
if not hasattr(cura.CuraVersion, "CuraCloudAPIRoot"): # type: ignore
|
||||
return self.DEFAULT_CLOUD_API_ROOT
|
||||
if not cura.CuraVersion.CuraCloudAPIRoot: # type: ignore
|
||||
return self.DEFAULT_CLOUD_API_ROOT
|
||||
return cura.CuraVersion.CuraCloudAPIRoot # type: ignore
|
||||
|
||||
# Get the cloud API version from CuraVersion
|
||||
def _getCloudAPIVersion(self) -> int:
|
||||
if not hasattr(cura, "CuraVersion"):
|
||||
return self.DEFAULT_CLOUD_API_VERSION
|
||||
if not hasattr(cura.CuraVersion, "CuraCloudAPIVersion"): # type: ignore
|
||||
return self.DEFAULT_CLOUD_API_VERSION
|
||||
if not cura.CuraVersion.CuraCloudAPIVersion: # type: ignore
|
||||
return self.DEFAULT_CLOUD_API_VERSION
|
||||
return cura.CuraVersion.CuraCloudAPIVersion # type: ignore
|
||||
|
||||
# Get the packages version depending on Cura version settings.
|
||||
def _getSDKVersion(self) -> Union[int, str]:
|
||||
if not hasattr(cura, "CuraVersion"):
|
||||
return self._application.getAPIVersion().getMajor()
|
||||
if not hasattr(cura.CuraVersion, "CuraSDKVersion"): # type: ignore
|
||||
return self._application.getAPIVersion().getMajor()
|
||||
if not cura.CuraVersion.CuraSDKVersion: # type: ignore
|
||||
return self._application.getAPIVersion().getMajor()
|
||||
return cura.CuraVersion.CuraSDKVersion # type: ignore
|
||||
|
||||
@pyqtSlot()
|
||||
def browsePackages(self) -> None:
|
||||
# Create the network manager:
|
||||
|
|
|
@ -15,6 +15,7 @@ Item
|
|||
id: base
|
||||
|
||||
property bool expanded: false
|
||||
property bool enabled: true
|
||||
property var borderWidth: 1
|
||||
property color borderColor: "#CCCCCC"
|
||||
property color headerBackgroundColor: "white"
|
||||
|
@ -34,7 +35,7 @@ Item
|
|||
color: borderColor
|
||||
width: borderWidth
|
||||
}
|
||||
color: headerMouseArea.containsMouse ? headerHoverColor : headerBackgroundColor
|
||||
color: base.enabled && headerMouseArea.containsMouse ? headerHoverColor : headerBackgroundColor
|
||||
height: childrenRect.height
|
||||
width: parent.width
|
||||
Behavior on color
|
||||
|
@ -50,8 +51,12 @@ Item
|
|||
{
|
||||
id: headerMouseArea
|
||||
anchors.fill: header
|
||||
onClicked: base.expanded = !base.expanded
|
||||
hoverEnabled: true
|
||||
onClicked:
|
||||
{
|
||||
if (!base.enabled) return
|
||||
base.expanded = !base.expanded
|
||||
}
|
||||
hoverEnabled: base.enabled
|
||||
}
|
||||
|
||||
Rectangle
|
||||
|
|
|
@ -18,7 +18,7 @@ import UM 1.3 as UM
|
|||
Item
|
||||
{
|
||||
// The buildplate name
|
||||
property alias buildplate: buildplateLabel.text
|
||||
property var buildplate: null
|
||||
|
||||
// Height is one 18px label/icon
|
||||
height: 18 * screenScaleFactor // TODO: Theme!
|
||||
|
@ -34,7 +34,16 @@ Item
|
|||
Item
|
||||
{
|
||||
height: parent.height
|
||||
width: 32 * screenScaleFactor // TODO: Theme! (Should be same as extruder icon width)
|
||||
width: 32 * screenScaleFactor // Ensure the icon is centered under the extruder icon (same width)
|
||||
|
||||
Rectangle
|
||||
{
|
||||
anchors.centerIn: parent
|
||||
height: parent.height
|
||||
width: height
|
||||
color: buildplateIcon.visible > 0 ? "transparent" : "#eeeeee" // TODO: Theme!
|
||||
radius: Math.floor(height / 2)
|
||||
}
|
||||
|
||||
UM.RecolorImage
|
||||
{
|
||||
|
@ -44,6 +53,7 @@ Item
|
|||
height: parent.height
|
||||
source: "../svg/icons/buildplate.svg"
|
||||
width: height
|
||||
visible: buildplate
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,7 +63,8 @@ Item
|
|||
color: "#191919" // TODO: Theme!
|
||||
elide: Text.ElideRight
|
||||
font: UM.Theme.getFont("default") // 12pt, regular
|
||||
text: ""
|
||||
text: buildplate ? buildplate : ""
|
||||
visible: text !== ""
|
||||
|
||||
// FIXED-LINE-HEIGHT:
|
||||
height: 18 * screenScaleFactor // TODO: Theme!
|
||||
|
|
|
@ -14,7 +14,12 @@ Item
|
|||
property var tileWidth: 834 * screenScaleFactor // TODO: Theme!
|
||||
property var tileHeight: 216 * screenScaleFactor // TODO: Theme!
|
||||
property var tileSpacing: 60 * screenScaleFactor // TODO: Theme!
|
||||
property var maxOffset: (OutputDevice.printers.length - 1) * (tileWidth + tileSpacing)
|
||||
|
||||
// Array/model of printers to populate the carousel with
|
||||
property var printers: []
|
||||
|
||||
// Maximum distance the carousel can be shifted
|
||||
property var maxOffset: (printers.length - 1) * (tileWidth + tileSpacing)
|
||||
|
||||
height: centerSection.height
|
||||
width: maximumWidth
|
||||
|
@ -129,7 +134,7 @@ Item
|
|||
|
||||
Repeater
|
||||
{
|
||||
model: OutputDevice.printers
|
||||
model: printers
|
||||
MonitorPrinterCard
|
||||
{
|
||||
printer: modelData
|
||||
|
@ -151,7 +156,7 @@ Item
|
|||
width: 36 * screenScaleFactor // TODO: Theme!
|
||||
height: 72 * screenScaleFactor // TODO: Theme!
|
||||
z: 10
|
||||
visible: currentIndex < OutputDevice.printers.length - 1
|
||||
visible: currentIndex < printers.length - 1
|
||||
onClicked: navigateTo(currentIndex + 1)
|
||||
hoverEnabled: true
|
||||
background: Rectangle
|
||||
|
@ -225,9 +230,10 @@ Item
|
|||
topMargin: 36 * screenScaleFactor // TODO: Theme!
|
||||
}
|
||||
spacing: 8 * screenScaleFactor // TODO: Theme!
|
||||
visible: printers.length > 1
|
||||
Repeater
|
||||
{
|
||||
model: OutputDevice.printers
|
||||
model: printers
|
||||
Button
|
||||
{
|
||||
background: Rectangle
|
||||
|
@ -243,7 +249,7 @@ Item
|
|||
}
|
||||
|
||||
function navigateTo( i ) {
|
||||
if (i >= 0 && i < OutputDevice.printers.length)
|
||||
if (i >= 0 && i < printers.length)
|
||||
{
|
||||
tiles.x = -1 * i * (tileWidth + tileSpacing)
|
||||
currentIndex = i
|
||||
|
|
|
@ -54,7 +54,7 @@ UM.Dialog
|
|||
wrapMode: Text.WordWrap
|
||||
text:
|
||||
{
|
||||
if (!printer.activePrintJob)
|
||||
if (!printer || !printer.activePrintJob)
|
||||
{
|
||||
return ""
|
||||
}
|
||||
|
|
|
@ -39,38 +39,62 @@ Item
|
|||
color: "#eeeeee" // TODO: Theme!
|
||||
position: 0
|
||||
}
|
||||
Label
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: materialLabel
|
||||
id: materialLabelWrapper
|
||||
anchors
|
||||
{
|
||||
left: extruderIcon.right
|
||||
leftMargin: 12 * screenScaleFactor // TODO: Theme!
|
||||
}
|
||||
color: materialLabel.visible > 0 ? "transparent" : "#eeeeee" // TODO: Theme!
|
||||
height: 18 * screenScaleFactor // TODO: Theme!
|
||||
width: Math.max(materialLabel.contentWidth, 60 * screenScaleFactor) // TODO: Theme!
|
||||
radius: 2 * screenScaleFactor // TODO: Theme!
|
||||
|
||||
Label
|
||||
{
|
||||
id: materialLabel
|
||||
|
||||
color: "#191919" // TODO: Theme!
|
||||
elide: Text.ElideRight
|
||||
font: UM.Theme.getFont("default") // 12pt, regular
|
||||
text: ""
|
||||
visible: text !== ""
|
||||
|
||||
// FIXED-LINE-HEIGHT:
|
||||
height: 18 * screenScaleFactor // TODO: Theme!
|
||||
height: parent.height
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: printCoreLabelWrapper
|
||||
anchors
|
||||
{
|
||||
left: materialLabelWrapper.left
|
||||
bottom: parent.bottom
|
||||
}
|
||||
color: printCoreLabel.visible > 0 ? "transparent" : "#eeeeee" // TODO: Theme!
|
||||
height: 18 * screenScaleFactor // TODO: Theme!
|
||||
width: Math.max(printCoreLabel.contentWidth, 36 * screenScaleFactor) // TODO: Theme!
|
||||
radius: 2 * screenScaleFactor // TODO: Theme!
|
||||
|
||||
Label
|
||||
{
|
||||
id: printCoreLabel
|
||||
anchors
|
||||
{
|
||||
left: materialLabel.left
|
||||
bottom: parent.bottom
|
||||
}
|
||||
|
||||
color: "#191919" // TODO: Theme!
|
||||
elide: Text.ElideRight
|
||||
font: UM.Theme.getFont("default_bold") // 12pt, bold
|
||||
text: ""
|
||||
visible: text !== ""
|
||||
|
||||
// FIXED-LINE-HEIGHT:
|
||||
height: 18 * screenScaleFactor // TODO: Theme!
|
||||
height: parent.height
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
}
|
|
@ -56,5 +56,6 @@ Item
|
|||
x: Math.round(size * 0.25) * screenScaleFactor
|
||||
y: Math.round(size * 0.15625) * screenScaleFactor
|
||||
// TODO: Once 'size' is themed, screenScaleFactor won't be needed
|
||||
visible: position >= 0
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ Item
|
|||
|
||||
ExpandableCard
|
||||
{
|
||||
enabled: printJob != null
|
||||
borderColor: printJob.configurationChanges.length !== 0 ? "#f5a623" : "#CCCCCC" // TODO: Theme!
|
||||
headerItem: Row
|
||||
{
|
||||
|
@ -41,33 +42,57 @@ Item
|
|||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: 18 * screenScaleFactor // TODO: Theme!
|
||||
width: 216 * screenScaleFactor // TODO: Theme! (Should match column size)
|
||||
Rectangle
|
||||
{
|
||||
color: "#eeeeee"
|
||||
width: Math.round(parent.width / 2)
|
||||
height: parent.height
|
||||
visible: !printJob
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: printJob && printJob.name ? printJob.name : ""
|
||||
color: "#374355"
|
||||
elide: Text.ElideRight
|
||||
font: UM.Theme.getFont("medium") // 14pt, regular
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 216 * screenScaleFactor // TODO: Theme! (Should match column size)
|
||||
visible: printJob
|
||||
|
||||
// FIXED-LINE-HEIGHT:
|
||||
height: 18 * screenScaleFactor // TODO: Theme!
|
||||
height: parent.height
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: 18 * screenScaleFactor // TODO: Theme!
|
||||
width: 216 * screenScaleFactor // TODO: Theme! (Should match column size)
|
||||
Rectangle
|
||||
{
|
||||
color: "#eeeeee"
|
||||
width: Math.round(parent.width / 3)
|
||||
height: parent.height
|
||||
visible: !printJob
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: printJob ? OutputDevice.formatDuration(printJob.timeTotal) : ""
|
||||
color: "#374355"
|
||||
elide: Text.ElideRight
|
||||
font: UM.Theme.getFont("medium") // 14pt, regular
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 216 * screenScaleFactor // TODO: Theme! (Should match column size)
|
||||
visible: printJob
|
||||
|
||||
// FIXED-LINE-HEIGHT:
|
||||
height: 18 * screenScaleFactor // TODO: Theme!
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
|
@ -75,6 +100,14 @@ Item
|
|||
height: 18 * screenScaleFactor // TODO: This should be childrenRect.height but QML throws warnings
|
||||
width: childrenRect.width
|
||||
|
||||
Rectangle
|
||||
{
|
||||
color: "#eeeeee"
|
||||
width: 72 * screenScaleFactor // TODO: Theme!
|
||||
height: parent.height
|
||||
visible: !printJob
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: printerAssignmentLabel
|
||||
|
@ -100,7 +133,7 @@ Item
|
|||
width: 120 * screenScaleFactor // TODO: Theme!
|
||||
|
||||
// FIXED-LINE-HEIGHT:
|
||||
height: 18 * screenScaleFactor // TODO: Theme!
|
||||
height: parent.height
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
|
@ -115,6 +148,7 @@ Item
|
|||
}
|
||||
height: childrenRect.height
|
||||
spacing: 6 // TODO: Theme!
|
||||
visible: printJob
|
||||
|
||||
Repeater
|
||||
{
|
||||
|
|
|
@ -16,7 +16,11 @@ Item
|
|||
width: size
|
||||
height: size
|
||||
|
||||
// Actual content
|
||||
Rectangle
|
||||
{
|
||||
anchors.fill: parent
|
||||
color: printJob ? "transparent" : "#eeeeee" // TODO: Theme!
|
||||
radius: 8 // TODO: Theme!
|
||||
Image
|
||||
{
|
||||
id: previewImage
|
||||
|
@ -30,8 +34,9 @@ Item
|
|||
return 1.0
|
||||
}
|
||||
source: printJob ? printJob.previewImageUrl : ""
|
||||
visible: printJob
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
UM.RecolorImage
|
||||
{
|
||||
|
|
|
@ -34,16 +34,16 @@ Item
|
|||
{
|
||||
background: Rectangle
|
||||
{
|
||||
color: printJob && printJob.isActive ? "#e4e4f2" : "#f3f3f9" // TODO: Theme!
|
||||
color: "#f5f5f5" // TODO: Theme!
|
||||
implicitHeight: visible ? 8 * screenScaleFactor : 0 // TODO: Theme!
|
||||
implicitWidth: 180 * screenScaleFactor // TODO: Theme!
|
||||
radius: 4 * screenScaleFactor // TODO: Theme!
|
||||
radius: 2 * screenScaleFactor // TODO: Theme!
|
||||
}
|
||||
progress: Rectangle
|
||||
{
|
||||
id: progressItem;
|
||||
color: printJob && printJob.isActive ? "#0a0850" : "#9392b2" // TODO: Theme!
|
||||
radius: 4 * screenScaleFactor // TODO: Theme!
|
||||
color: printJob && printJob.isActive ? "#3282ff" : "#CCCCCC" // TODO: Theme!
|
||||
radius: 2 * screenScaleFactor // TODO: Theme!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,16 +33,24 @@ Item
|
|||
width: 834 * screenScaleFactor // TODO: Theme!
|
||||
height: childrenRect.height
|
||||
|
||||
// Printer portion
|
||||
Rectangle
|
||||
{
|
||||
id: printerInfo
|
||||
id: background
|
||||
anchors.fill: parent
|
||||
color: "#FFFFFF" // TODO: Theme!
|
||||
border
|
||||
{
|
||||
color: "#CCCCCC" // TODO: Theme!
|
||||
width: borderSize // TODO: Remove once themed
|
||||
}
|
||||
color: "white" // TODO: Theme!
|
||||
radius: 2 * screenScaleFactor // TODO: Theme!
|
||||
}
|
||||
|
||||
// Printer portion
|
||||
Item
|
||||
{
|
||||
id: printerInfo
|
||||
|
||||
width: parent.width
|
||||
height: 144 * screenScaleFactor // TODO: Theme!
|
||||
|
||||
|
@ -56,15 +64,22 @@ Item
|
|||
}
|
||||
spacing: 18 * screenScaleFactor // TODO: Theme!
|
||||
|
||||
Image
|
||||
Rectangle
|
||||
{
|
||||
id: printerImage
|
||||
width: 108 * screenScaleFactor // TODO: Theme!
|
||||
height: 108 * screenScaleFactor // TODO: Theme!
|
||||
color: printer ? "transparent" : "#eeeeee" // TODO: Theme!
|
||||
radius: 8 // TODO: Theme!
|
||||
Image
|
||||
{
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: "../png/" + printer.type + ".png"
|
||||
source: printer ? "../png/" + printer.type + ".png" : ""
|
||||
mipmap: true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Item
|
||||
{
|
||||
|
@ -75,20 +90,38 @@ Item
|
|||
width: 180 * screenScaleFactor // TODO: Theme!
|
||||
height: printerNameLabel.height + printerFamilyPill.height + 6 * screenScaleFactor // TODO: Theme!
|
||||
|
||||
Label
|
||||
Rectangle
|
||||
{
|
||||
id: printerNameLabel
|
||||
// color: "#414054" // TODO: Theme!
|
||||
color: printer ? "transparent" : "#eeeeee" // TODO: Theme!
|
||||
height: 18 * screenScaleFactor // TODO: Theme!
|
||||
width: parent.width
|
||||
radius: 2 * screenScaleFactor // TODO: Theme!
|
||||
|
||||
Label
|
||||
{
|
||||
text: printer && printer.name ? printer.name : ""
|
||||
color: "#414054" // TODO: Theme!
|
||||
elide: Text.ElideRight
|
||||
font: UM.Theme.getFont("large_bold") // 16pt, bold
|
||||
font: UM.Theme.getFont("large") // 16pt, bold
|
||||
width: parent.width
|
||||
visible: printer
|
||||
|
||||
// FIXED-LINE-HEIGHT:
|
||||
height: 18 * screenScaleFactor // TODO: Theme!
|
||||
height: parent.height
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
color: "#eeeeee" // TODO: Theme!
|
||||
height: 18 * screenScaleFactor // TODO: Theme!
|
||||
radius: 2 * screenScaleFactor // TODO: Theme!
|
||||
visible: !printer
|
||||
width: 48 * screenScaleFactor // TODO: Theme!
|
||||
}
|
||||
MonitorPrinterPill
|
||||
{
|
||||
id: printerFamilyPill
|
||||
|
@ -98,7 +131,7 @@ Item
|
|||
topMargin: 6 * screenScaleFactor // TODO: Theme!
|
||||
left: printerNameLabel.left
|
||||
}
|
||||
text: printer.type
|
||||
text: printer ? printer.type : ""
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,15 +139,29 @@ Item
|
|||
{
|
||||
id: printerConfiguration
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
buildplate: "Glass"
|
||||
buildplate: printer ? "Glass" : null // 'Glass' as a default
|
||||
configurations:
|
||||
[
|
||||
base.printer.printerConfiguration.extruderConfigurations[0],
|
||||
base.printer.printerConfiguration.extruderConfigurations[1]
|
||||
]
|
||||
height: 72 * screenScaleFactor // TODO: Theme!
|
||||
{
|
||||
var configs = []
|
||||
if (printer)
|
||||
{
|
||||
configs.push(printer.printerConfiguration.extruderConfigurations[0])
|
||||
configs.push(printer.printerConfiguration.extruderConfigurations[1])
|
||||
}
|
||||
else
|
||||
{
|
||||
configs.push(null, null)
|
||||
}
|
||||
return configs
|
||||
}
|
||||
height: 72 * screenScaleFactor // TODO: Theme!te theRect's x property
|
||||
}
|
||||
|
||||
// TODO: Make this work.
|
||||
PropertyAnimation { target: printerConfiguration; property: "visible"; to: 0; loops: Animation.Infinite; duration: 500 }
|
||||
}
|
||||
|
||||
|
||||
|
||||
PrintJobContextMenu
|
||||
{
|
||||
|
@ -126,10 +173,11 @@ Item
|
|||
top: parent.top
|
||||
topMargin: 12 * screenScaleFactor // TODO: Theme!
|
||||
}
|
||||
printJob: printer.activePrintJob
|
||||
printJob: printer ? printer.activePrintJob : null
|
||||
width: 36 * screenScaleFactor // TODO: Theme!
|
||||
height: 36 * screenScaleFactor // TODO: Theme!
|
||||
enabled: base.enabled
|
||||
visible: printer
|
||||
}
|
||||
CameraButton
|
||||
{
|
||||
|
@ -143,10 +191,24 @@ Item
|
|||
}
|
||||
iconSource: "../svg/icons/camera.svg"
|
||||
enabled: base.enabled
|
||||
visible: printer
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Divider
|
||||
Rectangle
|
||||
{
|
||||
anchors
|
||||
{
|
||||
top: printJobInfo.top
|
||||
left: printJobInfo.left
|
||||
right: printJobInfo.right
|
||||
}
|
||||
height: borderSize // Remove once themed
|
||||
color: background.border.color
|
||||
}
|
||||
|
||||
// Print job portion
|
||||
Rectangle
|
||||
{
|
||||
|
@ -158,10 +220,10 @@ Item
|
|||
}
|
||||
border
|
||||
{
|
||||
color: printer.activePrintJob && printer.activePrintJob.configurationChanges.length > 0 ? "#f5a623" : "#CCCCCC" // TODO: Theme!
|
||||
color: printer && printer.activePrintJob && printer.activePrintJob.configurationChanges.length > 0 ? "#f5a623" : "transparent" // TODO: Theme!
|
||||
width: borderSize // TODO: Remove once themed
|
||||
}
|
||||
color: "white" // TODO: Theme!
|
||||
color: "transparent" // TODO: Theme!
|
||||
height: 84 * screenScaleFactor + borderSize // TODO: Remove once themed
|
||||
width: parent.width
|
||||
|
||||
|
@ -184,9 +246,12 @@ Item
|
|||
{
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
color: "#414054" // TODO: Theme!
|
||||
color: printer ? "#414054" : "#aaaaaa" // TODO: Theme!
|
||||
font: UM.Theme.getFont("large_bold") // 16pt, bold
|
||||
text: {
|
||||
if (!printer) {
|
||||
return catalog.i18nc("@label:status", "Loading...")
|
||||
}
|
||||
if (printer && printer.state == "disabled")
|
||||
{
|
||||
return catalog.i18nc("@label:status", "Unavailable")
|
||||
|
@ -215,10 +280,10 @@ Item
|
|||
MonitorPrintJobPreview
|
||||
{
|
||||
anchors.centerIn: parent
|
||||
printJob: base.printer.activePrintJob
|
||||
printJob: printer ? printer.activePrintJob : null
|
||||
size: parent.height
|
||||
}
|
||||
visible: printer.activePrintJob
|
||||
visible: printer && printer.activePrintJob && !printerStatus.visible
|
||||
}
|
||||
|
||||
Item
|
||||
|
@ -229,15 +294,15 @@ Item
|
|||
}
|
||||
width: 180 * screenScaleFactor // TODO: Theme!
|
||||
height: printerNameLabel.height + printerFamilyPill.height + 6 * screenScaleFactor // TODO: Theme!
|
||||
visible: printer.activePrintJob
|
||||
visible: printer && printer.activePrintJob && !printerStatus.visible
|
||||
|
||||
Label
|
||||
{
|
||||
id: printerJobNameLabel
|
||||
color: printer.activePrintJob && printer.activePrintJob.isActive ? "#414054" : "#babac1" // TODO: Theme!
|
||||
color: printer && printer.activePrintJob && printer.activePrintJob.isActive ? "#414054" : "#babac1" // TODO: Theme!
|
||||
elide: Text.ElideRight
|
||||
font: UM.Theme.getFont("large_bold") // 16pt, bold
|
||||
text: base.printer.activePrintJob ? base.printer.activePrintJob.name : "Untitled" // TODO: I18N
|
||||
font: UM.Theme.getFont("large") // 16pt, bold
|
||||
text: printer && printer.activePrintJob ? printer.activePrintJob.name : "Untitled" // TODO: I18N
|
||||
width: parent.width
|
||||
|
||||
// FIXED-LINE-HEIGHT:
|
||||
|
@ -254,10 +319,10 @@ Item
|
|||
topMargin: 6 * screenScaleFactor // TODO: Theme!
|
||||
left: printerJobNameLabel.left
|
||||
}
|
||||
color: printer.activePrintJob && printer.activePrintJob.isActive ? "#53657d" : "#babac1" // TODO: Theme!
|
||||
color: printer && printer.activePrintJob && printer.activePrintJob.isActive ? "#53657d" : "#babac1" // TODO: Theme!
|
||||
elide: Text.ElideRight
|
||||
font: UM.Theme.getFont("default") // 12pt, regular
|
||||
text: printer.activePrintJob ? printer.activePrintJob.owner : "Anonymous" // TODO: I18N
|
||||
text: printer && printer.activePrintJob ? printer.activePrintJob.owner : "Anonymous" // TODO: I18N
|
||||
width: parent.width
|
||||
|
||||
// FIXED-LINE-HEIGHT:
|
||||
|
@ -272,8 +337,8 @@ Item
|
|||
{
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
printJob: printer.activePrintJob
|
||||
visible: printer.activePrintJob && printer.activePrintJob.configurationChanges.length === 0
|
||||
printJob: printer && printer.activePrintJob
|
||||
visible: printer && printer.activePrintJob && printer.activePrintJob.configurationChanges.length === 0 && !printerStatus.visible
|
||||
}
|
||||
|
||||
Label
|
||||
|
@ -284,7 +349,7 @@ Item
|
|||
}
|
||||
font: UM.Theme.getFont("default")
|
||||
text: "Requires configuration changes"
|
||||
visible: printer.activePrintJob && printer.activePrintJob.configurationChanges.length > 0
|
||||
visible: printer && printer.activePrintJob && printer.activePrintJob.configurationChanges.length > 0 && !printerStatus.visible
|
||||
|
||||
// FIXED-LINE-HEIGHT:
|
||||
height: 18 * screenScaleFactor // TODO: Theme!
|
||||
|
@ -326,7 +391,7 @@ Item
|
|||
}
|
||||
implicitHeight: 32 * screenScaleFactor // TODO: Theme!
|
||||
implicitWidth: 96 * screenScaleFactor // TODO: Theme!
|
||||
visible: printer.activePrintJob && printer.activePrintJob.configurationChanges.length > 0
|
||||
visible: printer && printer.activePrintJob && printer.activePrintJob.configurationChanges.length > 0 && !printerStatus.visible
|
||||
onClicked: base.enabled ? overrideConfirmationDialog.open() : {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ Item
|
|||
property alias buildplate: buildplateConfig.buildplate
|
||||
|
||||
// Array of extracted extruder configurations
|
||||
property var configurations: null
|
||||
property var configurations: [null,null]
|
||||
|
||||
// Default size, but should be stretched to fill parent
|
||||
height: 72 * parent.height
|
||||
|
@ -37,10 +37,10 @@ Item
|
|||
|
||||
MonitorExtruderConfiguration
|
||||
{
|
||||
color: modelData.activeMaterial ? modelData.activeMaterial.color : "#eeeeee" // TODO: Theme!
|
||||
material: modelData.activeMaterial ? modelData.activeMaterial.name : ""
|
||||
position: modelData.position
|
||||
printCore: modelData.hotendID
|
||||
color: modelData && modelData.activeMaterial ? modelData.activeMaterial.color : "#eeeeee" // TODO: Theme!
|
||||
material: modelData && modelData.activeMaterial ? modelData.activeMaterial.name : ""
|
||||
position: modelData && typeof(modelData.position) === "number" ? modelData.position : -1 // Use negative one to create empty extruder number
|
||||
printCore: modelData ? modelData.hotendID : ""
|
||||
|
||||
// Keep things responsive!
|
||||
width: Math.floor((base.width - (configurations.length - 1) * extruderConfigurationRow.spacing) / configurations.length)
|
||||
|
@ -53,6 +53,6 @@ Item
|
|||
{
|
||||
id: buildplateConfig
|
||||
anchors.bottom: parent.bottom
|
||||
buildplate: "Glass" // 'Glass' as a default
|
||||
buildplate: null
|
||||
}
|
||||
}
|
|
@ -27,12 +27,12 @@ Item
|
|||
}
|
||||
|
||||
implicitHeight: 18 * screenScaleFactor // TODO: Theme!
|
||||
implicitWidth: printerNameLabel.contentWidth + 12 // TODO: Theme!
|
||||
implicitWidth: Math.max(printerNameLabel.contentWidth + 12 * screenScaleFactor, 36 * screenScaleFactor) // TODO: Theme!
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
anchors.fill: parent
|
||||
color: "#e4e4f2" // TODO: Theme!
|
||||
color: printerNameLabel.visible ? "#e4e4f2" : "#eeeeee"// TODO: Theme!
|
||||
radius: 2 * screenScaleFactor // TODO: Theme!
|
||||
}
|
||||
|
||||
|
@ -41,6 +41,7 @@ Item
|
|||
anchors.centerIn: parent
|
||||
color: "#535369" // TODO: Theme!
|
||||
text: tagText
|
||||
font.pointSize: 10
|
||||
font.pointSize: 10 // TODO: Theme!
|
||||
visible: text !== ""
|
||||
}
|
||||
}
|
|
@ -42,8 +42,8 @@ Item
|
|||
{
|
||||
id: externalLinkIcon
|
||||
anchors.verticalCenter: manageQueueLabel.verticalCenter
|
||||
color: UM.Theme.getColor("primary")
|
||||
source: "../svg/icons/external_link.svg"
|
||||
color: UM.Theme.getColor("text_link")
|
||||
source: UM.Theme.getIcon("external_link")
|
||||
width: 16 * screenScaleFactor // TODO: Theme! (Y U NO USE 18 LIKE ALL OTHER ICONS?!)
|
||||
height: 16 * screenScaleFactor // TODO: Theme! (Y U NO USE 18 LIKE ALL OTHER ICONS?!)
|
||||
}
|
||||
|
@ -56,10 +56,11 @@ Item
|
|||
leftMargin: 6 * screenScaleFactor // TODO: Theme!
|
||||
verticalCenter: externalLinkIcon.verticalCenter
|
||||
}
|
||||
color: UM.Theme.getColor("primary")
|
||||
color: UM.Theme.getColor("text_link")
|
||||
font: UM.Theme.getFont("default") // 12pt, regular
|
||||
linkColor: UM.Theme.getColor("primary")
|
||||
linkColor: UM.Theme.getColor("text_link")
|
||||
text: catalog.i18nc("@label link to connect manager", "Manage queue in Cura Connect")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -144,7 +145,6 @@ Item
|
|||
topMargin: 12 * screenScaleFactor // TODO: Theme!
|
||||
}
|
||||
style: UM.Theme.styles.scrollview
|
||||
visible: OutputDevice.receivedPrintJobs
|
||||
width: parent.width
|
||||
|
||||
ListView
|
||||
|
@ -160,7 +160,7 @@ Item
|
|||
}
|
||||
printJob: modelData
|
||||
}
|
||||
model: OutputDevice.queuedPrintJobs
|
||||
model: OutputDevice.receivedPrintJobs ? OutputDevice.queuedPrintJobs : [null,null]
|
||||
spacing: 6 // TODO: Theme!
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,8 +64,10 @@ Component
|
|||
}
|
||||
width: parent.width
|
||||
height: 264 * screenScaleFactor // TODO: Theme!
|
||||
MonitorCarousel {
|
||||
MonitorCarousel
|
||||
{
|
||||
id: carousel
|
||||
printers: OutputDevice.receivedPrintJobs ? OutputDevice.printers : [null]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -90,67 +90,4 @@ Item {
|
|||
source: "DiscoverUM3Action.qml";
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent;
|
||||
objectName: "networkPrinterConnectionInfo";
|
||||
spacing: UM.Theme.getSize("default_margin").width;
|
||||
visible: isUM3;
|
||||
|
||||
Button {
|
||||
onClicked: Cura.MachineManager.printerOutputDevices[0].requestAuthentication();
|
||||
text: catalog.i18nc("@action:button", "Request Access");
|
||||
tooltip: catalog.i18nc("@info:tooltip", "Send access request to the printer");
|
||||
visible: printerConnected && !printerAcceptsCommands && !authenticationRequested;
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors {
|
||||
left: parent.left;
|
||||
right: parent.right;
|
||||
}
|
||||
height: childrenRect.height;
|
||||
spacing: UM.Theme.getSize("default_margin").width;
|
||||
visible: printerConnected;
|
||||
|
||||
Column {
|
||||
Repeater {
|
||||
model: CuraApplication.getExtrudersModel()
|
||||
|
||||
Label {
|
||||
text: model.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
Repeater {
|
||||
id: nozzleColumn;
|
||||
model: hotendIds
|
||||
|
||||
Label {
|
||||
text: nozzleColumn.model[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
Repeater {
|
||||
id: materialColumn;
|
||||
model: materialNames
|
||||
|
||||
Label {
|
||||
text: materialColumn.model[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
onClicked: manager.loadConfigurationFromPrinter();
|
||||
text: catalog.i18nc("@action:button", "Activate Configuration");
|
||||
tooltip: catalog.i18nc("@info:tooltip", "Load the configuration of the printer into Cura");
|
||||
visible: false; // printerConnected && !isClusterPrinter()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -193,4 +193,3 @@ class DiscoverUM3Action(MachineAction):
|
|||
|
||||
# Create extra components
|
||||
CuraApplication.getInstance().addAdditionalComponent("monitorButtons", self.__additional_components_view.findChild(QObject, "networkPrinterConnectButton"))
|
||||
CuraApplication.getInstance().addAdditionalComponent("machinesDetailPane", self.__additional_components_view.findChild(QObject, "networkPrinterConnectionInfo"))
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
import json
|
||||
import os
|
||||
import urllib.parse
|
||||
from typing import Dict, TYPE_CHECKING, Set
|
||||
|
||||
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
|
||||
|
@ -10,9 +9,7 @@ from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
|
|||
from UM.Application import Application
|
||||
from UM.Job import Job
|
||||
from UM.Logger import Logger
|
||||
from UM.MimeTypeDatabase import MimeTypeDatabase
|
||||
from UM.Resources import Resources
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
# Absolute imports don't work in plugins
|
||||
from .Models import ClusterMaterial, LocalMaterial
|
||||
|
||||
|
@ -37,7 +34,6 @@ class SendMaterialJob(Job):
|
|||
#
|
||||
# \param reply The reply from the printer, a json file.
|
||||
def _onGetRemoteMaterials(self, reply: QNetworkReply) -> None:
|
||||
|
||||
# Got an error from the HTTP request. If we did not receive a 200 something happened.
|
||||
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
|
||||
Logger.log("e", "Error fetching materials from printer: %s", reply.errorString())
|
||||
|
@ -52,7 +48,6 @@ class SendMaterialJob(Job):
|
|||
#
|
||||
# \param remote_materials_by_guid The remote materials by GUID.
|
||||
def _sendMissingMaterials(self, remote_materials_by_guid: Dict[str, ClusterMaterial]) -> None:
|
||||
|
||||
# Collect local materials
|
||||
local_materials_by_guid = self._getLocalMaterials()
|
||||
if len(local_materials_by_guid) == 0:
|
||||
|
@ -91,22 +86,22 @@ class SendMaterialJob(Job):
|
|||
#
|
||||
# \param materials_to_send A set with id's of materials that must be sent.
|
||||
def _sendMaterials(self, materials_to_send: Set[str]) -> None:
|
||||
file_paths = Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.MaterialInstanceContainer)
|
||||
container_registry = Application.getInstance().getContainerRegistry()
|
||||
material_manager = Application.getInstance().getMaterialManager()
|
||||
material_group_dict = material_manager.getAllMaterialGroups()
|
||||
|
||||
# Find all local material files and send them if needed.
|
||||
for file_path in file_paths:
|
||||
try:
|
||||
mime_type = MimeTypeDatabase.getMimeTypeForFile(file_path)
|
||||
except MimeTypeDatabase.MimeTypeNotFoundError:
|
||||
continue
|
||||
|
||||
file_name = os.path.basename(file_path)
|
||||
material_id = urllib.parse.unquote_plus(mime_type.stripExtension(file_name))
|
||||
if material_id not in materials_to_send:
|
||||
for root_material_id in material_group_dict:
|
||||
if root_material_id not in materials_to_send:
|
||||
# If the material does not have to be sent we skip it.
|
||||
continue
|
||||
|
||||
self._sendMaterialFile(file_path, file_name, material_id)
|
||||
file_path = container_registry.getContainerFilePathById(root_material_id)
|
||||
if not file_path:
|
||||
Logger.log("w", "Cannot get file path for material container [%s]", root_material_id)
|
||||
continue
|
||||
|
||||
file_name = os.path.basename(file_path)
|
||||
self._sendMaterialFile(file_path, file_name, root_material_id)
|
||||
|
||||
## Send a single material file to the printer.
|
||||
#
|
||||
|
@ -116,7 +111,6 @@ class SendMaterialJob(Job):
|
|||
# \param file_name The name of the material file.
|
||||
# \param material_id The ID of the material in the file.
|
||||
def _sendMaterialFile(self, file_path: str, file_name: str, material_id: str) -> None:
|
||||
|
||||
parts = []
|
||||
|
||||
# Add the material file.
|
||||
|
@ -171,27 +165,31 @@ class SendMaterialJob(Job):
|
|||
# \return a dictionary of LocalMaterial objects by GUID
|
||||
def _getLocalMaterials(self) -> Dict[str, LocalMaterial]:
|
||||
result = {} # type: Dict[str, LocalMaterial]
|
||||
container_registry = Application.getInstance().getContainerRegistry()
|
||||
material_containers = container_registry.findContainersMetadata(type = "material")
|
||||
material_manager = Application.getInstance().getMaterialManager()
|
||||
|
||||
material_group_dict = material_manager.getAllMaterialGroups()
|
||||
|
||||
# Find the latest version of all material containers in the registry.
|
||||
for material in material_containers:
|
||||
for root_material_id, material_group in material_group_dict.items():
|
||||
material_metadata = material_group.root_material_node.getMetadata()
|
||||
|
||||
try:
|
||||
# material version must be an int
|
||||
material["version"] = int(material["version"])
|
||||
material_metadata["version"] = int(material_metadata["version"])
|
||||
|
||||
# Create a new local material
|
||||
local_material = LocalMaterial(**material)
|
||||
local_material = LocalMaterial(**material_metadata)
|
||||
local_material.id = root_material_id
|
||||
|
||||
if local_material.GUID not in result or \
|
||||
local_material.version > result.get(local_material.GUID).version:
|
||||
result[local_material.GUID] = local_material
|
||||
|
||||
except KeyError:
|
||||
Logger.logException("w", "Local material {} has missing values.".format(material["id"]))
|
||||
Logger.logException("w", "Local material {} has missing values.".format(material_metadata["id"]))
|
||||
except ValueError:
|
||||
Logger.logException("w", "Local material {} has invalid values.".format(material["id"]))
|
||||
Logger.logException("w", "Local material {} has invalid values.".format(material_metadata["id"]))
|
||||
except TypeError:
|
||||
Logger.logException("w", "Local material {} has invalid values.".format(material["id"]))
|
||||
Logger.logException("w", "Local material {} has invalid values.".format(material_metadata["id"]))
|
||||
|
||||
return result
|
||||
|
|
|
@ -114,6 +114,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
|||
if key == um_network_key:
|
||||
if not self._discovered_devices[key].isConnected():
|
||||
Logger.log("d", "Attempting to connect with [%s]" % key)
|
||||
active_machine.setMetaDataEntry("connection_type", self._discovered_devices[key].getConnectionType().value)
|
||||
self._discovered_devices[key].connect()
|
||||
self._discovered_devices[key].connectionStateChanged.connect(self._onDeviceConnectionStateChanged)
|
||||
else:
|
||||
|
|
|
@ -1,26 +1,29 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
import copy
|
||||
import io
|
||||
import json
|
||||
from unittest import TestCase, mock
|
||||
from unittest.mock import patch, call
|
||||
from unittest.mock import patch, call, MagicMock
|
||||
|
||||
from PyQt5.QtCore import QByteArray
|
||||
|
||||
from UM.MimeTypeDatabase import MimeType
|
||||
from UM.Application import Application
|
||||
|
||||
from cura.Machines.MaterialGroup import MaterialGroup
|
||||
from cura.Machines.MaterialNode import MaterialNode
|
||||
|
||||
from plugins.UM3NetworkPrinting.src.SendMaterialJob import SendMaterialJob
|
||||
|
||||
_FILES_MAP = {"generic_pla_white": "/materials/generic_pla_white.xml.fdm_material",
|
||||
"generic_pla_black": "/materials/generic_pla_black.xml.fdm_material",
|
||||
}
|
||||
|
||||
|
||||
@patch("builtins.open", lambda _, __: io.StringIO("<xml></xml>"))
|
||||
@patch("UM.MimeTypeDatabase.MimeTypeDatabase.getMimeTypeForFile",
|
||||
lambda _: MimeType(name = "application/x-ultimaker-material-profile", comment = "Ultimaker Material Profile",
|
||||
suffixes = ["xml.fdm_material"]))
|
||||
@patch("UM.Resources.Resources.getAllResourcesOfType", lambda _: ["/materials/generic_pla_white.xml.fdm_material"])
|
||||
@patch("plugins.UM3NetworkPrinting.src.ClusterUM3OutputDevice")
|
||||
@patch("PyQt5.QtNetwork.QNetworkReply")
|
||||
class TestSendMaterialJob(TestCase):
|
||||
# version 1
|
||||
_LOCAL_MATERIAL_WHITE = {"type": "material", "status": "unknown", "id": "generic_pla_white",
|
||||
"base_file": "generic_pla_white", "setting_version": "5", "name": "White PLA",
|
||||
"brand": "Generic", "material": "PLA", "color_name": "White",
|
||||
|
@ -29,6 +32,37 @@ class TestSendMaterialJob(TestCase):
|
|||
"properties": {"density": "1.00", "diameter": "2.85", "weight": "750"},
|
||||
"definition": "fdmprinter", "compatible": True}
|
||||
|
||||
# version 2
|
||||
_LOCAL_MATERIAL_WHITE_NEWER = {"type": "material", "status": "unknown", "id": "generic_pla_white",
|
||||
"base_file": "generic_pla_white", "setting_version": "5", "name": "White PLA",
|
||||
"brand": "Generic", "material": "PLA", "color_name": "White",
|
||||
"GUID": "badb0ee7-87c8-4f3f-9398-938587b67dce", "version": "2",
|
||||
"color_code": "#ffffff",
|
||||
"description": "Test PLA White", "adhesion_info": "Use glue.",
|
||||
"approximate_diameter": "3",
|
||||
"properties": {"density": "1.00", "diameter": "2.85", "weight": "750"},
|
||||
"definition": "fdmprinter", "compatible": True}
|
||||
|
||||
# invalid version: "one"
|
||||
_LOCAL_MATERIAL_WHITE_INVALID_VERSION = {"type": "material", "status": "unknown", "id": "generic_pla_white",
|
||||
"base_file": "generic_pla_white", "setting_version": "5", "name": "White PLA",
|
||||
"brand": "Generic", "material": "PLA", "color_name": "White",
|
||||
"GUID": "badb0ee7-87c8-4f3f-9398-938587b67dce", "version": "one",
|
||||
"color_code": "#ffffff",
|
||||
"description": "Test PLA White", "adhesion_info": "Use glue.",
|
||||
"approximate_diameter": "3",
|
||||
"properties": {"density": "1.00", "diameter": "2.85", "weight": "750"},
|
||||
"definition": "fdmprinter", "compatible": True}
|
||||
|
||||
_LOCAL_MATERIAL_WHITE_ALL_RESULT = {"generic_pla_white": MaterialGroup("generic_pla_white",
|
||||
MaterialNode(_LOCAL_MATERIAL_WHITE))}
|
||||
|
||||
_LOCAL_MATERIAL_WHITE_NEWER_ALL_RESULT = {"generic_pla_white": MaterialGroup("generic_pla_white",
|
||||
MaterialNode(_LOCAL_MATERIAL_WHITE_NEWER))}
|
||||
|
||||
_LOCAL_MATERIAL_WHITE_INVALID_VERSION_ALL_RESULT = {"generic_pla_white": MaterialGroup("generic_pla_white",
|
||||
MaterialNode(_LOCAL_MATERIAL_WHITE_INVALID_VERSION))}
|
||||
|
||||
_LOCAL_MATERIAL_BLACK = {"type": "material", "status": "unknown", "id": "generic_pla_black",
|
||||
"base_file": "generic_pla_black", "setting_version": "5", "name": "Yellow CPE",
|
||||
"brand": "Ultimaker", "material": "CPE", "color_name": "Black",
|
||||
|
@ -37,6 +71,9 @@ class TestSendMaterialJob(TestCase):
|
|||
"properties": {"density": "1.01", "diameter": "2.85", "weight": "750"},
|
||||
"definition": "fdmprinter", "compatible": True}
|
||||
|
||||
_LOCAL_MATERIAL_BLACK_ALL_RESULT = {"generic_pla_black": MaterialGroup("generic_pla_black",
|
||||
MaterialNode(_LOCAL_MATERIAL_BLACK))}
|
||||
|
||||
_REMOTE_MATERIAL_WHITE = {
|
||||
"guid": "badb0ee7-87c8-4f3f-9398-938587b67dce",
|
||||
"material": "PLA",
|
||||
|
@ -55,14 +92,17 @@ class TestSendMaterialJob(TestCase):
|
|||
"density": 1.00
|
||||
}
|
||||
|
||||
def test_run(self, device_mock, reply_mock):
|
||||
def test_run(self):
|
||||
device_mock = MagicMock()
|
||||
job = SendMaterialJob(device_mock)
|
||||
job.run()
|
||||
|
||||
# We expect the materials endpoint to be called when the job runs.
|
||||
device_mock.get.assert_called_with("materials/", on_finished = job._onGetRemoteMaterials)
|
||||
|
||||
def test__onGetRemoteMaterials_withFailedRequest(self, reply_mock, device_mock):
|
||||
def test__onGetRemoteMaterials_withFailedRequest(self):
|
||||
reply_mock = MagicMock()
|
||||
device_mock = MagicMock()
|
||||
reply_mock.attribute.return_value = 404
|
||||
job = SendMaterialJob(device_mock)
|
||||
job._onGetRemoteMaterials(reply_mock)
|
||||
|
@ -70,7 +110,9 @@ class TestSendMaterialJob(TestCase):
|
|||
# We expect the device not to be called for any follow up.
|
||||
self.assertEqual(0, device_mock.createFormPart.call_count)
|
||||
|
||||
def test__onGetRemoteMaterials_withWrongEncoding(self, reply_mock, device_mock):
|
||||
def test__onGetRemoteMaterials_withWrongEncoding(self):
|
||||
reply_mock = MagicMock()
|
||||
device_mock = MagicMock()
|
||||
reply_mock.attribute.return_value = 200
|
||||
reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("cp500"))
|
||||
job = SendMaterialJob(device_mock)
|
||||
|
@ -79,7 +121,9 @@ class TestSendMaterialJob(TestCase):
|
|||
# Given that the parsing fails we do no expect the device to be called for any follow up.
|
||||
self.assertEqual(0, device_mock.createFormPart.call_count)
|
||||
|
||||
def test__onGetRemoteMaterials_withBadJsonAnswer(self, reply_mock, device_mock):
|
||||
def test__onGetRemoteMaterials_withBadJsonAnswer(self):
|
||||
reply_mock = MagicMock()
|
||||
device_mock = MagicMock()
|
||||
reply_mock.attribute.return_value = 200
|
||||
reply_mock.readAll.return_value = QByteArray(b"Six sick hicks nick six slick bricks with picks and sticks.")
|
||||
job = SendMaterialJob(device_mock)
|
||||
|
@ -88,7 +132,9 @@ class TestSendMaterialJob(TestCase):
|
|||
# Given that the parsing fails we do no expect the device to be called for any follow up.
|
||||
self.assertEqual(0, device_mock.createFormPart.call_count)
|
||||
|
||||
def test__onGetRemoteMaterials_withMissingGuidInRemoteMaterial(self, reply_mock, device_mock):
|
||||
def test__onGetRemoteMaterials_withMissingGuidInRemoteMaterial(self):
|
||||
reply_mock = MagicMock()
|
||||
device_mock = MagicMock()
|
||||
reply_mock.attribute.return_value = 200
|
||||
remote_material_without_guid = self._REMOTE_MATERIAL_WHITE.copy()
|
||||
del remote_material_without_guid["guid"]
|
||||
|
@ -99,18 +145,20 @@ class TestSendMaterialJob(TestCase):
|
|||
# Given that parsing fails we do not expect the device to be called for any follow up.
|
||||
self.assertEqual(0, device_mock.createFormPart.call_count)
|
||||
|
||||
@patch("cura.Machines.MaterialManager.MaterialManager")
|
||||
@patch("cura.Settings.CuraContainerRegistry")
|
||||
@patch("UM.Application")
|
||||
def test__onGetRemoteMaterials_withInvalidVersionInLocalMaterial(self, application_mock, container_registry_mock,
|
||||
reply_mock, device_mock):
|
||||
material_manager_mock):
|
||||
reply_mock = MagicMock()
|
||||
device_mock = MagicMock()
|
||||
application_mock.getContainerRegistry.return_value = container_registry_mock
|
||||
application_mock.getMaterialManager.return_value = material_manager_mock
|
||||
|
||||
reply_mock.attribute.return_value = 200
|
||||
reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("ascii"))
|
||||
|
||||
localMaterialWhiteWithInvalidVersion = self._LOCAL_MATERIAL_WHITE.copy()
|
||||
localMaterialWhiteWithInvalidVersion["version"] = "one"
|
||||
container_registry_mock.findContainersMetadata.return_value = [localMaterialWhiteWithInvalidVersion]
|
||||
|
||||
application_mock.getContainerRegistry.return_value = container_registry_mock
|
||||
material_manager_mock.getAllMaterialGroups.return_value = self._LOCAL_MATERIAL_WHITE_INVALID_VERSION_ALL_RESULT.copy()
|
||||
|
||||
with mock.patch.object(Application, "getInstance", new = lambda: application_mock):
|
||||
job = SendMaterialJob(device_mock)
|
||||
|
@ -118,15 +166,16 @@ class TestSendMaterialJob(TestCase):
|
|||
|
||||
self.assertEqual(0, device_mock.createFormPart.call_count)
|
||||
|
||||
@patch("cura.Settings.CuraContainerRegistry")
|
||||
@patch("UM.Application")
|
||||
def test__onGetRemoteMaterials_withNoUpdate(self, application_mock, container_registry_mock, reply_mock,
|
||||
device_mock):
|
||||
application_mock.getContainerRegistry.return_value = container_registry_mock
|
||||
@patch("UM.Application.Application.getInstance")
|
||||
def test__onGetRemoteMaterials_withNoUpdate(self, application_mock):
|
||||
reply_mock = MagicMock()
|
||||
device_mock = MagicMock()
|
||||
container_registry_mock = application_mock.getContainerRegistry.return_value
|
||||
material_manager_mock = application_mock.getMaterialManager.return_value
|
||||
|
||||
device_mock.createFormPart.return_value = "_xXx_"
|
||||
|
||||
container_registry_mock.findContainersMetadata.return_value = [self._LOCAL_MATERIAL_WHITE]
|
||||
material_manager_mock.getAllMaterialGroups.return_value = self._LOCAL_MATERIAL_WHITE_ALL_RESULT.copy()
|
||||
|
||||
reply_mock.attribute.return_value = 200
|
||||
reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("ascii"))
|
||||
|
@ -138,22 +187,23 @@ class TestSendMaterialJob(TestCase):
|
|||
self.assertEqual(0, device_mock.createFormPart.call_count)
|
||||
self.assertEqual(0, device_mock.postFormWithParts.call_count)
|
||||
|
||||
@patch("cura.Settings.CuraContainerRegistry")
|
||||
@patch("UM.Application")
|
||||
def test__onGetRemoteMaterials_withUpdatedMaterial(self, application_mock, container_registry_mock, reply_mock,
|
||||
device_mock):
|
||||
application_mock.getContainerRegistry.return_value = container_registry_mock
|
||||
@patch("UM.Application.Application.getInstance")
|
||||
def test__onGetRemoteMaterials_withUpdatedMaterial(self, get_instance_mock):
|
||||
reply_mock = MagicMock()
|
||||
device_mock = MagicMock()
|
||||
application_mock = get_instance_mock.return_value
|
||||
container_registry_mock = application_mock.getContainerRegistry.return_value
|
||||
material_manager_mock = application_mock.getMaterialManager.return_value
|
||||
|
||||
container_registry_mock.getContainerFilePathById = lambda x: _FILES_MAP.get(x)
|
||||
|
||||
device_mock.createFormPart.return_value = "_xXx_"
|
||||
|
||||
localMaterialWhiteWithHigherVersion = self._LOCAL_MATERIAL_WHITE.copy()
|
||||
localMaterialWhiteWithHigherVersion["version"] = "2"
|
||||
container_registry_mock.findContainersMetadata.return_value = [localMaterialWhiteWithHigherVersion]
|
||||
material_manager_mock.getAllMaterialGroups.return_value = self._LOCAL_MATERIAL_WHITE_NEWER_ALL_RESULT.copy()
|
||||
|
||||
reply_mock.attribute.return_value = 200
|
||||
reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("ascii"))
|
||||
|
||||
with mock.patch.object(Application, "getInstance", new = lambda: application_mock):
|
||||
job = SendMaterialJob(device_mock)
|
||||
job._onGetRemoteMaterials(reply_mock)
|
||||
|
||||
|
@ -164,16 +214,21 @@ class TestSendMaterialJob(TestCase):
|
|||
call.postFormWithParts(target = "materials/", parts = ["_xXx_"], on_finished = job.sendingFinished)],
|
||||
device_mock.method_calls)
|
||||
|
||||
@patch("cura.Settings.CuraContainerRegistry")
|
||||
@patch("UM.Application")
|
||||
def test__onGetRemoteMaterials_withNewMaterial(self, application_mock, container_registry_mock, reply_mock,
|
||||
device_mock):
|
||||
application_mock.getContainerRegistry.return_value = container_registry_mock
|
||||
@patch("UM.Application.Application.getInstance")
|
||||
def test__onGetRemoteMaterials_withNewMaterial(self, application_mock):
|
||||
reply_mock = MagicMock()
|
||||
device_mock = MagicMock()
|
||||
container_registry_mock = application_mock.getContainerRegistry.return_value
|
||||
material_manager_mock = application_mock.getMaterialManager.return_value
|
||||
|
||||
container_registry_mock.getContainerFilePathById = lambda x: _FILES_MAP.get(x)
|
||||
|
||||
device_mock.createFormPart.return_value = "_xXx_"
|
||||
|
||||
container_registry_mock.findContainersMetadata.return_value = [self._LOCAL_MATERIAL_WHITE,
|
||||
self._LOCAL_MATERIAL_BLACK]
|
||||
all_results = self._LOCAL_MATERIAL_WHITE_ALL_RESULT.copy()
|
||||
for key, value in self._LOCAL_MATERIAL_BLACK_ALL_RESULT.items():
|
||||
all_results[key] = value
|
||||
material_manager_mock.getAllMaterialGroups.return_value = all_results
|
||||
|
||||
reply_mock.attribute.return_value = 200
|
||||
reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_BLACK]).encode("ascii"))
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
import configparser
|
||||
from typing import Tuple, List, Set
|
||||
import io
|
||||
from UM.VersionUpgrade import VersionUpgrade
|
||||
from cura.PrinterOutputDevice import ConnectionType
|
||||
deleted_settings = {"bridge_wall_max_overhang"} # type: Set[str]
|
||||
|
||||
|
||||
class VersionUpgrade35to40(VersionUpgrade):
|
||||
# Upgrades stacks to have the new version number.
|
||||
def upgradeStack(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
|
||||
parser = configparser.ConfigParser(interpolation=None)
|
||||
parser.read_string(serialized)
|
||||
|
||||
# Update version number.
|
||||
parser["general"]["version"] = "4"
|
||||
parser["metadata"]["setting_version"] = "6"
|
||||
|
||||
if parser["metadata"].get("um_network_key") is not None or parser["metadata"].get("octoprint_api_key") is not None:
|
||||
# Set the connection type if um_network_key or the octoprint key is set.
|
||||
parser["metadata"]["connection_type"] = str(ConnectionType.NetworkConnection.value)
|
||||
|
||||
result = io.StringIO()
|
||||
parser.write(result)
|
||||
return [filename], [result.getvalue()]
|
||||
pass
|
||||
|
||||
def getCfgVersion(self, serialised: str) -> int:
|
||||
parser = configparser.ConfigParser(interpolation = None)
|
||||
parser.read_string(serialised)
|
||||
format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
|
||||
setting_version = int(parser.get("metadata", "setting_version", fallback = "0"))
|
||||
return format_version * 1000000 + setting_version
|
||||
|
||||
## Upgrades Preferences to have the new version number.
|
||||
def upgradePreferences(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
|
||||
parser = configparser.ConfigParser(interpolation=None)
|
||||
parser.read_string(serialized)
|
||||
|
||||
if "metadata" not in parser:
|
||||
parser["metadata"] = {}
|
||||
parser["general"]["version"] = "6"
|
||||
parser["metadata"]["setting_version"] = "6"
|
||||
|
||||
result = io.StringIO()
|
||||
parser.write(result)
|
||||
return [filename], [result.getvalue()]
|
||||
|
||||
## Upgrades instance containers to have the new version
|
||||
# number.
|
||||
def upgradeInstanceContainer(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
|
||||
parser = configparser.ConfigParser(interpolation=None)
|
||||
parser.read_string(serialized)
|
||||
|
||||
# Update version number.
|
||||
parser["general"]["version"] = "4"
|
||||
parser["metadata"]["setting_version"] = "6"
|
||||
|
||||
#self._resetConcentric3DInfillPattern(parser)
|
||||
if "values" in parser:
|
||||
for deleted_setting in deleted_settings:
|
||||
if deleted_setting not in parser["values"]:
|
||||
continue
|
||||
del parser["values"][deleted_setting]
|
||||
|
||||
result = io.StringIO()
|
||||
parser.write(result)
|
||||
return [filename], [result.getvalue()]
|
56
plugins/VersionUpgrade/VersionUpgrade35to40/__init__.py
Normal file
56
plugins/VersionUpgrade/VersionUpgrade35to40/__init__.py
Normal file
|
@ -0,0 +1,56 @@
|
|||
from typing import Dict, Any
|
||||
|
||||
from . import VersionUpgrade35to40
|
||||
|
||||
upgrade = VersionUpgrade35to40.VersionUpgrade35to40()
|
||||
|
||||
|
||||
def getMetaData() -> Dict[str, Any]:
|
||||
return {
|
||||
"version_upgrade": {
|
||||
# From To Upgrade function
|
||||
("preferences", 6000005): ("preferences", 6000006, upgrade.upgradePreferences),
|
||||
|
||||
("definition_changes", 4000005): ("definition_changes", 4000006, upgrade.upgradeInstanceContainer),
|
||||
("quality_changes", 4000005): ("quality_changes", 4000006, upgrade.upgradeInstanceContainer),
|
||||
("quality", 4000005): ("quality", 4000006, upgrade.upgradeInstanceContainer),
|
||||
("user", 4000005): ("user", 4000006, upgrade.upgradeInstanceContainer),
|
||||
|
||||
("machine_stack", 4000005): ("machine_stack", 4000006, upgrade.upgradeStack),
|
||||
("extruder_train", 4000005): ("extruder_train", 4000006, upgrade.upgradeStack),
|
||||
},
|
||||
"sources": {
|
||||
"preferences": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"."}
|
||||
},
|
||||
"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) -> Dict[str, Any]:
|
||||
return {"version_upgrade": upgrade}
|
8
plugins/VersionUpgrade/VersionUpgrade35to40/plugin.json
Normal file
8
plugins/VersionUpgrade/VersionUpgrade35to40/plugin.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "Version Upgrade 3.5 to 4.0",
|
||||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Upgrades configurations from Cura 3.5 to Cura 4.0.",
|
||||
"api": "6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
|
@ -16,7 +16,7 @@ def getMetaData():
|
|||
"mimetype": "application/x-ultimaker-material-profile"
|
||||
},
|
||||
"version_upgrade": {
|
||||
("materials", 1000000): ("materials", 1000004, upgrader.upgradeMaterial),
|
||||
("materials", 1000000): ("materials", 1000006, upgrader.upgradeMaterial),
|
||||
},
|
||||
"sources": {
|
||||
"materials": {
|
||||
|
|
|
@ -50,6 +50,23 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"CuraDrive": {
|
||||
"package_info": {
|
||||
"package_id": "CuraDrive",
|
||||
"package_type": "plugin",
|
||||
"display_name": "Cura Backups",
|
||||
"description": "Backup and restore your configuration.",
|
||||
"package_version": "1.2.0",
|
||||
"sdk_version": 6,
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
"display_name": "Ultimaker B.V.",
|
||||
"email": "plugins@ultimaker.com",
|
||||
"website": "https://ultimaker.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
"CuraEngineBackend": {
|
||||
"package_info": {
|
||||
"package_id": "CuraEngineBackend",
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.1
|
||||
import QtGraphicalEffects 1.0 // For the dropshadow
|
||||
|
||||
import UM 1.1 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
|
@ -30,6 +31,7 @@ Button
|
|||
property color outlineDisabledColor: outlineColor
|
||||
property alias shadowColor: shadow.color
|
||||
property alias shadowEnabled: shadow.visible
|
||||
property alias busy: busyIndicator.visible
|
||||
|
||||
property alias toolTipContentAlignment: tooltip.contentAlignment
|
||||
|
||||
|
@ -55,7 +57,7 @@ Button
|
|||
width: visible ? height : 0
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
color: button.hovered ? button.textHoverColor : button.textColor
|
||||
color: button.enabled ? (button.hovered ? button.textHoverColor : button.textColor) : button.textDisabledColor
|
||||
visible: source != "" && !button.isIconOnRightSide
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
@ -117,4 +119,16 @@ Button
|
|||
id: tooltip
|
||||
visible: button.hovered
|
||||
}
|
||||
|
||||
BusyIndicator
|
||||
{
|
||||
id: busyIndicator
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
width: height
|
||||
height: parent.height
|
||||
|
||||
visible: false
|
||||
}
|
||||
}
|
|
@ -32,6 +32,13 @@ Column
|
|||
width: parent.width
|
||||
height: childrenRect.height
|
||||
|
||||
PrintInformationWidget
|
||||
{
|
||||
id: printInformationPanel
|
||||
visible: !preSlicedData
|
||||
anchors.right: parent.right
|
||||
}
|
||||
|
||||
Column
|
||||
{
|
||||
id: timeAndCostsInformation
|
||||
|
@ -50,15 +57,7 @@ Column
|
|||
|
||||
text: preSlicedData ? catalog.i18nc("@label", "No time estimation available") : PrintInformation.currentPrintTime.getDisplayString(UM.DurationFormat.Long)
|
||||
source: UM.Theme.getIcon("clock")
|
||||
font: UM.Theme.getFont("large_bold")
|
||||
|
||||
PrintInformationWidget
|
||||
{
|
||||
id: printInformationPanel
|
||||
visible: !preSlicedData
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: parent.contentWidth + UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
font: UM.Theme.getFont("medium_bold")
|
||||
}
|
||||
|
||||
Cura.IconWithText
|
||||
|
@ -91,43 +90,8 @@ Column
|
|||
return totalWeights + "g · " + totalLengths.toFixed(2) + "m"
|
||||
}
|
||||
source: UM.Theme.getIcon("spool")
|
||||
|
||||
Item
|
||||
{
|
||||
id: additionalComponents
|
||||
width: childrenRect.width
|
||||
anchors.right: parent.right
|
||||
height: parent.height
|
||||
Row
|
||||
{
|
||||
id: additionalComponentsRow
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
spacing: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
}
|
||||
Component.onCompleted: addAdditionalComponents("saveButton")
|
||||
|
||||
Connections
|
||||
{
|
||||
target: CuraApplication
|
||||
onAdditionalComponentsChanged: addAdditionalComponents("saveButton")
|
||||
}
|
||||
|
||||
function addAdditionalComponents (areaId)
|
||||
{
|
||||
if(areaId == "saveButton")
|
||||
{
|
||||
for (var component in CuraApplication.additionalComponents["saveButton"])
|
||||
{
|
||||
CuraApplication.additionalComponents["saveButton"][component].parent = additionalComponentsRow
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Item
|
||||
|
|
|
@ -110,8 +110,7 @@ Column
|
|||
|
||||
height: parent.height
|
||||
|
||||
anchors.right: additionalComponents.left
|
||||
anchors.rightMargin: additionalComponents.width != 0 ? UM.Theme.getSize("default_margin").width : 0
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
|
||||
text: catalog.i18nc("@button", "Slice")
|
||||
|
@ -128,45 +127,12 @@ Column
|
|||
height: parent.height
|
||||
anchors.left: parent.left
|
||||
|
||||
anchors.right: additionalComponents.left
|
||||
anchors.rightMargin: additionalComponents.width != 0 ? UM.Theme.getSize("default_margin").width : 0
|
||||
anchors.right: parent.right
|
||||
text: catalog.i18nc("@button", "Cancel")
|
||||
enabled: sliceButton.enabled
|
||||
visible: !sliceButton.visible
|
||||
onClicked: sliceOrStopSlicing()
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
id: additionalComponents
|
||||
width: childrenRect.width
|
||||
anchors.right: parent.right
|
||||
height: parent.height
|
||||
Row
|
||||
{
|
||||
id: additionalComponentsRow
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
}
|
||||
Component.onCompleted: prepareButtons.addAdditionalComponents("saveButton")
|
||||
|
||||
Connections
|
||||
{
|
||||
target: CuraApplication
|
||||
onAdditionalComponentsChanged: prepareButtons.addAdditionalComponents("saveButton")
|
||||
}
|
||||
|
||||
function addAdditionalComponents (areaId)
|
||||
{
|
||||
if(areaId == "saveButton")
|
||||
{
|
||||
for (var component in CuraApplication.additionalComponents["saveButton"])
|
||||
{
|
||||
CuraApplication.additionalComponents["saveButton"][component].parent = additionalComponentsRow
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -194,7 +160,7 @@ Column
|
|||
shortcut: "Ctrl+P"
|
||||
onTriggered:
|
||||
{
|
||||
if (prepareButton.enabled)
|
||||
if (sliceButton.enabled)
|
||||
{
|
||||
sliceOrStopSlicing()
|
||||
}
|
||||
|
|
63
resources/qml/CheckBoxWithTooltip.qml
Normal file
63
resources/qml/CheckBoxWithTooltip.qml
Normal file
|
@ -0,0 +1,63 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.1
|
||||
|
||||
import UM 1.3 as UM
|
||||
|
||||
CheckBox
|
||||
{
|
||||
id: checkbox
|
||||
hoverEnabled: true
|
||||
|
||||
property alias tooltip: tooltip.text
|
||||
|
||||
indicator: Rectangle
|
||||
{
|
||||
implicitWidth: UM.Theme.getSize("checkbox").width
|
||||
implicitHeight: UM.Theme.getSize("checkbox").height
|
||||
x: 0
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: UM.Theme.getColor("main_background")
|
||||
radius: UM.Theme.getSize("checkbox_radius").width
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: checkbox.hovered ? UM.Theme.getColor("checkbox_border_hover") : UM.Theme.getColor("checkbox_border")
|
||||
|
||||
UM.RecolorImage
|
||||
{
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: Math.round(parent.width / 2.5)
|
||||
height: Math.round(parent.height / 2.5)
|
||||
sourceSize.height: width
|
||||
color: UM.Theme.getColor("checkbox_mark")
|
||||
source: UM.Theme.getIcon("check")
|
||||
opacity: checkbox.checked
|
||||
Behavior on opacity { NumberAnimation { duration: 100; } }
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Label
|
||||
{
|
||||
anchors
|
||||
{
|
||||
left: checkbox.indicator.right
|
||||
leftMargin: UM.Theme.getSize("narrow_margin").width
|
||||
}
|
||||
text: checkbox.text
|
||||
color: UM.Theme.getColor("checkbox_text")
|
||||
font: UM.Theme.getFont("default")
|
||||
renderType: Text.NativeRendering
|
||||
elide: Text.ElideRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
ToolTip
|
||||
{
|
||||
id: tooltip
|
||||
text: ""
|
||||
delay: 500
|
||||
visible: text != "" && checkbox.hovered
|
||||
}
|
||||
}
|
|
@ -248,11 +248,59 @@ UM.MainWindow
|
|||
|
||||
Cura.ActionPanelWidget
|
||||
{
|
||||
id: actionPanelWidget
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.rightMargin: UM.Theme.getSize("thick_margin").width
|
||||
anchors.bottomMargin: UM.Theme.getSize("thick_margin").height
|
||||
visible: CuraApplication.platformActivity
|
||||
|
||||
/*
|
||||
Show this panel only if there is something on the build plate, and there is NOT an opaque item in front of the build plate.
|
||||
This cannot be solved by Z indexing! If you want to try solving this, please increase this counter when you're done:
|
||||
Number of people having tried to fix this by z-indexing: 2
|
||||
The problem arises from the following render order requirements:
|
||||
- The stage menu must be rendered above the stage main.
|
||||
- The stage main must be rendered above the action panel (because the monitor page must be rendered above the action panel).
|
||||
- The action panel must be rendered above the expandable components drop-down.
|
||||
However since the expandable components drop-downs are child elements of the stage menu,
|
||||
they can't be rendered lower than elements that are lower than the stage menu.
|
||||
Therefore we opted to forego the second requirement and hide the action panel instead when something obscures it (except the expandable components).
|
||||
We assume that QQuickRectangles are always opaque and any other item is not.
|
||||
*/
|
||||
visible: CuraApplication.platformActivity && (main.item == null || !qmlTypeOf(main.item, "QQuickRectangle"))
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
id: additionalComponents
|
||||
width: childrenRect.width
|
||||
anchors.right: actionPanelWidget.left
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.bottom: actionPanelWidget.bottom
|
||||
anchors.bottomMargin: UM.Theme.getSize("thick_margin").height * 2
|
||||
visible: actionPanelWidget.visible
|
||||
Row
|
||||
{
|
||||
id: additionalComponentsRow
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: contentItem.addAdditionalComponents()
|
||||
|
||||
Connections
|
||||
{
|
||||
target: CuraApplication
|
||||
onAdditionalComponentsChanged: contentItem.addAdditionalComponents("saveButton")
|
||||
}
|
||||
|
||||
function addAdditionalComponents()
|
||||
{
|
||||
for (var component in CuraApplication.additionalComponents["saveButton"])
|
||||
{
|
||||
CuraApplication.additionalComponents["saveButton"][component].parent = additionalComponentsRow
|
||||
}
|
||||
}
|
||||
|
||||
Loader
|
||||
|
@ -815,4 +863,21 @@ UM.MainWindow
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to check whether a QML object has a certain type.
|
||||
* Taken from StackOverflow: https://stackoverflow.com/a/28384228 and
|
||||
* adapted to our code style.
|
||||
* Licensed under CC BY-SA 3.0.
|
||||
* \param obj The QtObject to get the name of.
|
||||
* \param class_name (str) The name of the class to check against. Has to be
|
||||
* the QtObject class name, not the QML entity name.
|
||||
*/
|
||||
function qmlTypeOf(obj, class_name)
|
||||
{
|
||||
//className plus "(" is the class instance without modification.
|
||||
//className plus "_QML" is the class instance with user-defined properties.
|
||||
var str = obj.toString();
|
||||
return str.indexOf(class_name + "(") == 0 || str.indexOf(class_name + "_QML") == 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ Item
|
|||
}
|
||||
}
|
||||
|
||||
// This component will appear when there is no configurations (e.g. when losing connection)
|
||||
// This component will appear when there are no configurations (e.g. when losing connection or when they are being loaded)
|
||||
Item
|
||||
{
|
||||
width: parent.width
|
||||
|
@ -51,7 +51,11 @@ Item
|
|||
anchors.left: icon.right
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
text: catalog.i18nc("@label", "Downloading the configurations from the remote printer")
|
||||
// There are two cases that we want to diferenciate, one is when Cura is loading the configurations and the
|
||||
// other when the connection was lost
|
||||
text: Cura.MachineManager.printerConnected ?
|
||||
catalog.i18nc("@label", "Loading available configurations from the printer...") :
|
||||
catalog.i18nc("@label", "The configurations are not available because the printer is disconnected.")
|
||||
color: UM.Theme.getColor("text")
|
||||
font: UM.Theme.getFont("default")
|
||||
renderType: Text.NativeRendering
|
||||
|
|
|
@ -173,6 +173,59 @@ Cura.ExpandablePopup
|
|||
}
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
height: visible ? childrenRect.height: 0
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
width: childrenRect.width + UM.Theme.getSize("default_margin").width
|
||||
visible: popupItem.configuration_method == ConfigurationMenu.ConfigurationMethod.Custom
|
||||
UM.RecolorImage
|
||||
{
|
||||
id: externalLinkIcon
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
height: materialInfoLabel.height
|
||||
width: height
|
||||
sourceSize.height: width
|
||||
color: UM.Theme.getColor("text_link")
|
||||
source: UM.Theme.getIcon("external_link")
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: materialInfoLabel
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label", "See the material compatibility chart")
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text_link")
|
||||
linkColor: UM.Theme.getColor("text_link")
|
||||
anchors.left: externalLinkIcon.right
|
||||
anchors.leftMargin: UM.Theme.getSize("narrow_margin").width
|
||||
renderType: Text.NativeRendering
|
||||
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked:
|
||||
{
|
||||
// open the material URL with web browser
|
||||
var url = "https://ultimaker.com/incoming-links/cura/material-compatibilty"
|
||||
Qt.openUrlExternally(url)
|
||||
}
|
||||
onEntered:
|
||||
{
|
||||
materialInfoLabel.font.underline = true
|
||||
}
|
||||
onExited:
|
||||
{
|
||||
materialInfoLabel.font.underline = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: separator
|
||||
|
|
|
@ -126,132 +126,15 @@ UM.ManagementPage
|
|||
}
|
||||
}
|
||||
|
||||
Grid
|
||||
{
|
||||
id: machineInfo
|
||||
|
||||
anchors.top: machineActions.visible ? machineActions.bottom : machineActions.anchors.top
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
spacing: UM.Theme.getSize("default_margin").height
|
||||
rowSpacing: UM.Theme.getSize("default_lining").height
|
||||
columns: 2
|
||||
|
||||
visible: base.currentItem
|
||||
|
||||
property bool printerConnected: Cura.MachineManager.printerConnected
|
||||
property var connectedPrinter: printerConnected ? Cura.MachineManager.printerOutputDevices[0] : null
|
||||
property bool printerAcceptsCommands: printerConnected && Cura.MachineManager.printerOutputDevices[0].acceptsCommands
|
||||
property var printJob: connectedPrinter != null ? connectedPrinter.activePrintJob: null
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Printer type:")
|
||||
visible: base.currentItem && "definition_name" in base.currentItem.metadata
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: (base.currentItem && "definition_name" in base.currentItem.metadata) ? base.currentItem.metadata.definition_name : ""
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Connection:")
|
||||
visible: base.currentItem && base.currentItem.id == Cura.MachineManager.activeMachineId
|
||||
}
|
||||
Label
|
||||
{
|
||||
width: (parent.width * 0.7) | 0
|
||||
text: machineInfo.printerConnected ? machineInfo.connectedPrinter.connectionText : catalog.i18nc("@info:status", "The printer is not connected.")
|
||||
visible: base.currentItem && base.currentItem.id == Cura.MachineManager.activeMachineId
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "State:")
|
||||
visible: base.currentItem && base.currentItem.id == Cura.MachineManager.activeMachineId && machineInfo.printerAcceptsCommands
|
||||
}
|
||||
Label {
|
||||
width: (parent.width * 0.7) | 0
|
||||
text:
|
||||
{
|
||||
if(!machineInfo.printerConnected || !machineInfo.printerAcceptsCommands) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (machineInfo.printJob == null)
|
||||
{
|
||||
return catalog.i18nc("@label:MonitorStatus", "Waiting for a printjob");
|
||||
}
|
||||
|
||||
switch(machineInfo.printJob.state)
|
||||
{
|
||||
case "printing":
|
||||
return catalog.i18nc("@label:MonitorStatus", "Printing...");
|
||||
case "paused":
|
||||
return catalog.i18nc("@label:MonitorStatus", "Paused");
|
||||
case "pre_print":
|
||||
return catalog.i18nc("@label:MonitorStatus", "Preparing...");
|
||||
case "wait_cleanup":
|
||||
return catalog.i18nc("@label:MonitorStatus", "Waiting for someone to clear the build plate");
|
||||
case "error":
|
||||
return printerOutputDevice.errorText;
|
||||
case "maintenance":
|
||||
return catalog.i18nc("@label:MonitorStatus", "In maintenance. Please check the printer");
|
||||
case "abort": // note sure if this jobState actually occurs in the wild
|
||||
return catalog.i18nc("@label:MonitorStatus", "Aborting print...");
|
||||
|
||||
}
|
||||
return ""
|
||||
}
|
||||
visible: base.currentItem && base.currentItem.id == Cura.MachineManager.activeMachineId && machineInfo.printerAcceptsCommands
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: additionalComponentsColumn
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: machineInfo.visible ? machineInfo.bottom : machineInfo.anchors.top
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").width
|
||||
|
||||
spacing: UM.Theme.getSize("default_margin").width
|
||||
visible: base.currentItem && base.currentItem.id == Cura.MachineManager.activeMachineId
|
||||
|
||||
Component.onCompleted:
|
||||
{
|
||||
for (var component in CuraApplication.additionalComponents["machinesDetailPane"]) {
|
||||
CuraApplication.additionalComponents["machinesDetailPane"][component].parent = additionalComponentsColumn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
addAdditionalComponents("machinesDetailPane")
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: CuraApplication
|
||||
onAdditionalComponentsChanged: addAdditionalComponents
|
||||
}
|
||||
|
||||
function addAdditionalComponents (areaId) {
|
||||
if(areaId == "machinesDetailPane") {
|
||||
for (var component in CuraApplication.additionalComponents["machinesDetailPane"]) {
|
||||
CuraApplication.additionalComponents["machinesDetailPane"][component].parent = additionalComponentsColumn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UM.I18nCatalog { id: catalog; name: "cura"; }
|
||||
|
||||
UM.ConfirmRemoveDialog
|
||||
{
|
||||
id: confirmDialog;
|
||||
object: base.currentItem && base.currentItem.name ? base.currentItem.name : "";
|
||||
id: confirmDialog
|
||||
object: base.currentItem && base.currentItem.name ? base.currentItem.name : ""
|
||||
onYes:
|
||||
{
|
||||
Cura.MachineManager.removeMachine(base.currentItem.id);
|
||||
Cura.MachineManager.removeMachine(base.currentItem.id)
|
||||
if(!base.currentItem)
|
||||
{
|
||||
objectList.currentIndex = activeMachineIndex()
|
||||
|
|
|
@ -376,6 +376,7 @@ Item
|
|||
|
||||
width: true ? (parent.width * 0.4) | 0 : parent.width
|
||||
frameVisible: true
|
||||
clip: true
|
||||
|
||||
ListView
|
||||
{
|
||||
|
|
|
@ -176,6 +176,17 @@ Item
|
|||
UM.Preferences.setValue("view/settings_list_height", h);
|
||||
}
|
||||
}
|
||||
|
||||
UM.RecolorImage
|
||||
{
|
||||
width: parent.width * 0.05
|
||||
height: parent.height * 0.3
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
source: UM.Theme.getIcon("grip_lines")
|
||||
color: UM.Theme.getColor("lining")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,4 +15,5 @@ ViewsSelector 1.0 ViewsSelector.qml
|
|||
ToolbarButton 1.0 ToolbarButton.qml
|
||||
SettingView 1.0 SettingView.qml
|
||||
ProfileMenu 1.0 ProfileMenu.qml
|
||||
CheckBoxWithTooltip 1.0 CheckBoxWithTooltip.qml
|
||||
ToolTip 1.0 ToolTip.qml
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
4
resources/themes/cura-light/icons/grip_lines.svg
Normal file
4
resources/themes/cura-light/icons/grip_lines.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 6">
|
||||
<rect width="30" height="2" rx="1" ry="1" />
|
||||
<rect width="30" height="2" rx="1" ry="1" y="4" />
|
||||
</svg>
|
After Width: | Height: | Size: 170 B |
|
@ -478,7 +478,7 @@ QtObject
|
|||
color: (control.hovered || control._hovered) ? Theme.getColor("checkbox_hover") : (control.enabled ? Theme.getColor("checkbox") : Theme.getColor("checkbox_disabled"))
|
||||
Behavior on color { ColorAnimation { duration: 50; } }
|
||||
|
||||
radius: control.exclusiveGroup ? Math.round(Theme.getSize("checkbox").width / 2) : UM.Theme.getSize("checkbox_radius").width
|
||||
radius: control.exclusiveGroup ? Math.round(Theme.getSize("checkbox").width / 2) : Theme.getSize("checkbox_radius").width
|
||||
|
||||
border.width: Theme.getSize("default_lining").width
|
||||
border.color: (control.hovered || control._hovered) ? Theme.getColor("checkbox_border_hover") : Theme.getColor("checkbox_border")
|
||||
|
@ -585,6 +585,7 @@ QtObject
|
|||
text: control.unit ? control.unit : ""
|
||||
color: Theme.getColor("setting_unit");
|
||||
font: Theme.getFont("default");
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,21 @@
|
|||
"weight": 40,
|
||||
"family": "Noto Sans"
|
||||
},
|
||||
"large_ja_JP": {
|
||||
"size": 1.35,
|
||||
"weight": 50,
|
||||
"family": "Noto Sans"
|
||||
},
|
||||
"large_zh_CN": {
|
||||
"size": 1.35,
|
||||
"weight": 50,
|
||||
"family": "Noto Sans"
|
||||
},
|
||||
"large_zh_TW": {
|
||||
"size": 1.35,
|
||||
"weight": 50,
|
||||
"family": "Noto Sans"
|
||||
},
|
||||
"large_bold": {
|
||||
"size": 1.35,
|
||||
"weight": 63,
|
||||
|
@ -19,6 +34,21 @@
|
|||
"weight": 40,
|
||||
"family": "Noto Sans"
|
||||
},
|
||||
"medium_ja_JP": {
|
||||
"size": 1.16,
|
||||
"weight": 50,
|
||||
"family": "Noto Sans"
|
||||
},
|
||||
"medium_zh_CN": {
|
||||
"size": 1.16,
|
||||
"weight": 50,
|
||||
"family": "Noto Sans"
|
||||
},
|
||||
"medium_zh_TW": {
|
||||
"size": 1.16,
|
||||
"weight": 50,
|
||||
"family": "Noto Sans"
|
||||
},
|
||||
"medium_bold": {
|
||||
"size": 1.16,
|
||||
"weight": 63,
|
||||
|
@ -29,21 +59,84 @@
|
|||
"weight": 40,
|
||||
"family": "Noto Sans"
|
||||
},
|
||||
"default_ja_JP": {
|
||||
"size": 1.0,
|
||||
"weight": 50,
|
||||
"family": "Noto Sans"
|
||||
},
|
||||
"default_zh_CN": {
|
||||
"size": 1.0,
|
||||
"weight": 50,
|
||||
"family": "Noto Sans"
|
||||
},
|
||||
"default_zh_TW": {
|
||||
"size": 1.0,
|
||||
"weight": 50,
|
||||
"family": "Noto Sans"
|
||||
},
|
||||
"default_bold": {
|
||||
"size": 0.95,
|
||||
"weight": 63,
|
||||
"family": "Noto Sans"
|
||||
},
|
||||
"default_bold_ja_JP": {
|
||||
"size": 1.0,
|
||||
"weight": 63,
|
||||
"family": "Noto Sans"
|
||||
},
|
||||
"default_bold_zh_CN": {
|
||||
"size": 1.0,
|
||||
"weight": 63,
|
||||
"family": "Noto Sans"
|
||||
},
|
||||
"default_bold_zh_TW": {
|
||||
"size": 1.0,
|
||||
"weight": 63,
|
||||
"family": "Noto Sans"
|
||||
},
|
||||
"default_italic": {
|
||||
"size": 0.95,
|
||||
"weight": 40,
|
||||
"italic": true,
|
||||
"family": "Noto Sans"
|
||||
},
|
||||
"default_italic_ja_JP": {
|
||||
"size": 1.0,
|
||||
"weight": 50,
|
||||
"italic": true,
|
||||
"family": "Noto Sans"
|
||||
},
|
||||
"default_italic_zh_CN": {
|
||||
"size": 1.0,
|
||||
"weight": 50,
|
||||
"italic": true,
|
||||
"family": "Noto Sans"
|
||||
},
|
||||
"default_italic_zh_TW": {
|
||||
"size": 1.0,
|
||||
"weight": 50,
|
||||
"italic": true,
|
||||
"family": "Noto Sans"
|
||||
},
|
||||
"small": {
|
||||
"size": 0.7,
|
||||
"weight": 40,
|
||||
"family": "Noto Sans"
|
||||
},
|
||||
"small_ja_JP": {
|
||||
"size": 0.7,
|
||||
"weight": 50,
|
||||
"family": "Noto Sans"
|
||||
},
|
||||
"small_zh_CN": {
|
||||
"size": 0.7,
|
||||
"weight": 50,
|
||||
"family": "Noto Sans"
|
||||
},
|
||||
"small_zh_TW": {
|
||||
"size": 0.7,
|
||||
"weight": 50,
|
||||
"family": "Noto Sans"
|
||||
}
|
||||
},
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue