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/CuraBlenderPlugin
|
||||||
plugins/CuraCloudPlugin
|
plugins/CuraCloudPlugin
|
||||||
plugins/CuraDrivePlugin
|
plugins/CuraDrivePlugin
|
||||||
plugins/CuraDrive
|
|
||||||
plugins/CuraLiveScriptingPlugin
|
plugins/CuraLiveScriptingPlugin
|
||||||
plugins/CuraOpenSCADPlugin
|
plugins/CuraOpenSCADPlugin
|
||||||
plugins/CuraPrintProfileCreator
|
plugins/CuraPrintProfileCreator
|
||||||
|
|
|
@ -6,6 +6,7 @@ from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty
|
||||||
|
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
from UM.Message import Message
|
from UM.Message import Message
|
||||||
|
from cura import UltimakerCloudAuthentication
|
||||||
|
|
||||||
from cura.OAuth2.AuthorizationService import AuthorizationService
|
from cura.OAuth2.AuthorizationService import AuthorizationService
|
||||||
from cura.OAuth2.Models import OAuth2Settings
|
from cura.OAuth2.Models import OAuth2Settings
|
||||||
|
@ -37,15 +38,16 @@ class Account(QObject):
|
||||||
self._logged_in = False
|
self._logged_in = False
|
||||||
|
|
||||||
self._callback_port = 32118
|
self._callback_port = 32118
|
||||||
self._oauth_root = "https://account.ultimaker.com"
|
self._oauth_root = UltimakerCloudAuthentication.CuraCloudAccountAPIRoot
|
||||||
self._cloud_api_root = "https://api.ultimaker.com"
|
|
||||||
|
|
||||||
self._oauth_settings = OAuth2Settings(
|
self._oauth_settings = OAuth2Settings(
|
||||||
OAUTH_SERVER_URL= self._oauth_root,
|
OAUTH_SERVER_URL= self._oauth_root,
|
||||||
CALLBACK_PORT=self._callback_port,
|
CALLBACK_PORT=self._callback_port,
|
||||||
CALLBACK_URL="http://localhost:{}/callback".format(self._callback_port),
|
CALLBACK_URL="http://localhost:{}/callback".format(self._callback_port),
|
||||||
CLIENT_ID="um----------------------------ultimaker_cura",
|
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_DATA_PREFERENCE_KEY="general/ultimaker_auth_data",
|
||||||
AUTH_SUCCESS_REDIRECT="{}/app/auth-success".format(self._oauth_root),
|
AUTH_SUCCESS_REDIRECT="{}/app/auth-success".format(self._oauth_root),
|
||||||
AUTH_FAILED_REDIRECT="{}/app/auth-error".format(self._oauth_root)
|
AUTH_FAILED_REDIRECT="{}/app/auth-error".format(self._oauth_root)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
from typing import Tuple, Optional, TYPE_CHECKING
|
from typing import Tuple, Optional, TYPE_CHECKING, Dict, Any
|
||||||
|
|
||||||
from cura.Backups.BackupsManager import BackupsManager
|
from cura.Backups.BackupsManager import BackupsManager
|
||||||
|
|
||||||
|
@ -24,12 +24,12 @@ class Backups:
|
||||||
## Create a new back-up using the BackupsManager.
|
## Create a new back-up using the BackupsManager.
|
||||||
# \return Tuple containing a ZIP file with the back-up data and a dict
|
# \return Tuple containing a ZIP file with the back-up data and a dict
|
||||||
# with metadata about the back-up.
|
# 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()
|
return self.manager.createBackup()
|
||||||
|
|
||||||
## Restore a back-up using the BackupsManager.
|
## Restore a back-up using the BackupsManager.
|
||||||
# \param zip_file A ZIP file containing the actual back-up data.
|
# \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
|
# \param meta_data Some metadata needed for restoring a back-up, like the
|
||||||
# Cura version number.
|
# 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)
|
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.PrinterOutputDevice import PrinterOutputDevice
|
||||||
from cura.PrinterOutput.NetworkMJPGImage import NetworkMJPGImage
|
from cura.PrinterOutput.NetworkMJPGImage import NetworkMJPGImage
|
||||||
|
|
||||||
|
from cura import ApplicationMetadata
|
||||||
|
|
||||||
from UM.FlameProfiler import pyqtSlot
|
from UM.FlameProfiler import pyqtSlot
|
||||||
from UM.Decorators import override
|
from UM.Decorators import override
|
||||||
|
|
||||||
|
@ -164,11 +166,11 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(name = "cura",
|
super().__init__(name = "cura",
|
||||||
app_display_name = CuraAppDisplayName,
|
app_display_name = ApplicationMetadata.CuraAppDisplayName,
|
||||||
version = CuraVersion,
|
version = ApplicationMetadata.CuraVersion,
|
||||||
api_version = CuraSDKVersion,
|
api_version = ApplicationMetadata.CuraSDKVersion,
|
||||||
buildtype = CuraBuildType,
|
buildtype = ApplicationMetadata.CuraBuildType,
|
||||||
is_debug_mode = CuraDebugMode,
|
is_debug_mode = ApplicationMetadata.CuraDebugMode,
|
||||||
tray_icon_name = "cura-icon-32.png",
|
tray_icon_name = "cura-icon-32.png",
|
||||||
**kwargs)
|
**kwargs)
|
||||||
|
|
||||||
|
@ -500,7 +502,7 @@ class CuraApplication(QtApplication):
|
||||||
preferences.addPreference("cura/choice_on_profile_override", "always_ask")
|
preferences.addPreference("cura/choice_on_profile_override", "always_ask")
|
||||||
preferences.addPreference("cura/choice_on_open_project", "always_ask")
|
preferences.addPreference("cura/choice_on_open_project", "always_ask")
|
||||||
preferences.addPreference("cura/use_multi_build_plate", False)
|
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("view/settings_visible", False)
|
||||||
preferences.addPreference("cura/currency", "€")
|
preferences.addPreference("cura/currency", "€")
|
||||||
preferences.addPreference("cura/material_settings", "{}")
|
preferences.addPreference("cura/material_settings", "{}")
|
||||||
|
@ -955,7 +957,7 @@ class CuraApplication(QtApplication):
|
||||||
engine.rootContext().setContextProperty("CuraApplication", self)
|
engine.rootContext().setContextProperty("CuraApplication", self)
|
||||||
engine.rootContext().setContextProperty("PrintInformation", self._print_information)
|
engine.rootContext().setContextProperty("PrintInformation", self._print_information)
|
||||||
engine.rootContext().setContextProperty("CuraActions", self._cura_actions)
|
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")
|
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@"
|
CuraSDKVersion = "@CURA_SDK_VERSION@"
|
||||||
CuraCloudAPIRoot = "@CURA_CLOUD_API_ROOT@"
|
CuraCloudAPIRoot = "@CURA_CLOUD_API_ROOT@"
|
||||||
CuraCloudAPIVersion = "@CURA_CLOUD_API_VERSION@"
|
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]]:
|
def getMaterialGroupListByGUID(self, guid: str) -> Optional[List[MaterialGroup]]:
|
||||||
return self._guid_material_groups_map.get(guid)
|
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.
|
# 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)
|
@pyqtSlot(str)
|
||||||
def removeFavorite(self, root_material_id: str) -> None:
|
def removeFavorite(self, root_material_id: str) -> None:
|
||||||
self._favorites.remove(root_material_id)
|
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()
|
self.materialsUpdated.emit()
|
||||||
|
|
||||||
# Ensure all settings are saved.
|
# Ensure all settings are saved.
|
||||||
|
@ -688,4 +696,4 @@ class MaterialManager(QObject):
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def getFavorites(self):
|
def getFavorites(self):
|
||||||
return self._favorites
|
return self._favorites
|
||||||
|
|
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()
|
self._onChanged()
|
||||||
|
|
||||||
def _onProcessLayersFinished(self, job: ProcessSlicedLayersJob) -> None:
|
def _onProcessLayersFinished(self, job: ProcessSlicedLayersJob) -> None:
|
||||||
del self._stored_optimized_layer_data[job.getBuildPlate()]
|
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
|
self._process_layers_job = None
|
||||||
Logger.log("d", "See if there is more to slice(2)...")
|
Logger.log("d", "See if there is more to slice(2)...")
|
||||||
self._invokeSlice()
|
self._invokeSlice()
|
||||||
|
|
|
@ -57,7 +57,7 @@ class FirmwareUpdaterMachineAction(MachineAction):
|
||||||
outputDeviceCanUpdateFirmwareChanged = pyqtSignal()
|
outputDeviceCanUpdateFirmwareChanged = pyqtSignal()
|
||||||
@pyqtProperty(QObject, notify = outputDeviceCanUpdateFirmwareChanged)
|
@pyqtProperty(QObject, notify = outputDeviceCanUpdateFirmwareChanged)
|
||||||
def firmwareUpdater(self) -> Optional["FirmwareUpdater"]:
|
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()
|
self._active_firmware_updater = self._active_output_device.getFirmwareUpdater()
|
||||||
return self._active_firmware_updater
|
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 2.10
|
||||||
import QtQuick.Controls 1.4
|
import QtQuick.Controls 1.4
|
||||||
|
@ -7,31 +8,27 @@ import UM 1.3 as UM
|
||||||
import Cura 1.0 as Cura
|
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
|
id: viewportOverlay
|
||||||
Rectangle
|
|
||||||
|
color: UM.Theme.getColor("viewport_overlay")
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
// This mouse area is to prevent mouse clicks to be passed onto the scene.
|
||||||
|
MouseArea
|
||||||
{
|
{
|
||||||
id: viewportOverlay
|
|
||||||
|
|
||||||
color: UM.Theme.getColor("viewport_overlay")
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.AllButtons
|
||||||
// This mouse area is to prevent mouse clicks to be passed onto the scene.
|
onWheel: wheel.accepted = true
|
||||||
MouseArea
|
|
||||||
{
|
|
||||||
anchors.fill: parent
|
|
||||||
acceptedButtons: Qt.AllButtons
|
|
||||||
onWheel: wheel.accepted = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable dropping files into Cura when the monitor page is active
|
|
||||||
DropArea
|
|
||||||
{
|
|
||||||
anchors.fill: parent
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Disable dropping files into Cura when the monitor page is active
|
||||||
|
DropArea
|
||||||
|
{
|
||||||
|
anchors.fill: parent
|
||||||
|
}
|
||||||
Loader
|
Loader
|
||||||
{
|
{
|
||||||
id: monitorViewComponent
|
id: monitorViewComponent
|
||||||
|
@ -45,4 +42,4 @@ Item
|
||||||
|
|
||||||
sourceComponent: Cura.MachineManager.printerOutputDevices.length > 0 ? Cura.MachineManager.printerOutputDevices[0].monitorItem : null
|
sourceComponent: Cura.MachineManager.printerOutputDevices.length > 0 ? Cura.MachineManager.printerOutputDevices[0].monitorItem : null
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -488,7 +488,7 @@ UM.Dialog
|
||||||
{
|
{
|
||||||
objectName: "postProcessingSaveAreaButton"
|
objectName: "postProcessingSaveAreaButton"
|
||||||
visible: activeScriptsList.count > 0
|
visible: activeScriptsList.count > 0
|
||||||
height: UM.Theme.getSize("save_button_save_to_button").height
|
height: UM.Theme.getSize("action_button").height
|
||||||
width: height
|
width: height
|
||||||
tooltip: catalog.i18nc("@info:tooltip", "Change active post-processing scripts")
|
tooltip: catalog.i18nc("@info:tooltip", "Change active post-processing scripts")
|
||||||
onClicked: dialog.show()
|
onClicked: dialog.show()
|
||||||
|
|
|
@ -38,7 +38,7 @@ Window
|
||||||
{
|
{
|
||||||
id: mainView
|
id: mainView
|
||||||
width: parent.width
|
width: parent.width
|
||||||
z: -1
|
z: parent.z - 1
|
||||||
anchors
|
anchors
|
||||||
{
|
{
|
||||||
top: header.bottom
|
top: header.bottom
|
||||||
|
|
|
@ -91,5 +91,10 @@ Column
|
||||||
target: toolbox
|
target: toolbox
|
||||||
onInstallChanged: installed = toolbox.isInstalled(model.id)
|
onInstallChanged: installed = toolbox.isInstalled(model.id)
|
||||||
onMetadataChanged: canUpdate = toolbox.canUpdate(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
|
CheckBox
|
||||||
{
|
{
|
||||||
id: disableButton
|
id: disableButton
|
||||||
|
anchors.verticalCenter: pluginInfo.verticalCenter
|
||||||
checked: isEnabled
|
checked: isEnabled
|
||||||
visible: model.type == "plugin"
|
visible: model.type == "plugin"
|
||||||
width: visible ? UM.Theme.getSize("checkbox").width : 0
|
width: visible ? UM.Theme.getSize("checkbox").width : 0
|
||||||
|
|
|
@ -16,7 +16,8 @@ from UM.Extension import Extension
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
from UM.Version import Version
|
from UM.Version import Version
|
||||||
|
|
||||||
import cura
|
from cura import ApplicationMetadata
|
||||||
|
from cura import UltimakerCloudAuthentication
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
|
|
||||||
from .AuthorsModel import AuthorsModel
|
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
|
## The Toolbox class is responsible of communicating with the server through the API
|
||||||
class Toolbox(QObject, Extension):
|
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:
|
def __init__(self, application: CuraApplication) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self._application = application # type: CuraApplication
|
self._application = application # type: CuraApplication
|
||||||
|
|
||||||
self._sdk_version = None # type: Optional[Union[str, int]]
|
self._sdk_version = ApplicationMetadata.CuraSDKVersion # type: Union[str, int]
|
||||||
self._cloud_api_version = None # type: Optional[int]
|
self._cloud_api_version = UltimakerCloudAuthentication.CuraCloudAPIVersion # type: int
|
||||||
self._cloud_api_root = None # type: Optional[str]
|
self._cloud_api_root = UltimakerCloudAuthentication.CuraCloudAPIRoot # type: str
|
||||||
self._api_url = None # type: Optional[str]
|
self._api_url = None # type: Optional[str]
|
||||||
|
|
||||||
# Network:
|
# Network:
|
||||||
|
@ -182,9 +180,6 @@ class Toolbox(QObject, Extension):
|
||||||
def _onAppInitialized(self) -> None:
|
def _onAppInitialized(self) -> None:
|
||||||
self._plugin_registry = self._application.getPluginRegistry()
|
self._plugin_registry = self._application.getPluginRegistry()
|
||||||
self._package_manager = self._application.getPackageManager()
|
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(
|
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_root = self._cloud_api_root,
|
||||||
cloud_api_version = self._cloud_api_version,
|
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))
|
"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()
|
@pyqtSlot()
|
||||||
def browsePackages(self) -> None:
|
def browsePackages(self) -> None:
|
||||||
# Create the network manager:
|
# Create the network manager:
|
||||||
|
|
|
@ -15,6 +15,7 @@ Item
|
||||||
id: base
|
id: base
|
||||||
|
|
||||||
property bool expanded: false
|
property bool expanded: false
|
||||||
|
property bool enabled: true
|
||||||
property var borderWidth: 1
|
property var borderWidth: 1
|
||||||
property color borderColor: "#CCCCCC"
|
property color borderColor: "#CCCCCC"
|
||||||
property color headerBackgroundColor: "white"
|
property color headerBackgroundColor: "white"
|
||||||
|
@ -34,7 +35,7 @@ Item
|
||||||
color: borderColor
|
color: borderColor
|
||||||
width: borderWidth
|
width: borderWidth
|
||||||
}
|
}
|
||||||
color: headerMouseArea.containsMouse ? headerHoverColor : headerBackgroundColor
|
color: base.enabled && headerMouseArea.containsMouse ? headerHoverColor : headerBackgroundColor
|
||||||
height: childrenRect.height
|
height: childrenRect.height
|
||||||
width: parent.width
|
width: parent.width
|
||||||
Behavior on color
|
Behavior on color
|
||||||
|
@ -50,8 +51,12 @@ Item
|
||||||
{
|
{
|
||||||
id: headerMouseArea
|
id: headerMouseArea
|
||||||
anchors.fill: header
|
anchors.fill: header
|
||||||
onClicked: base.expanded = !base.expanded
|
onClicked:
|
||||||
hoverEnabled: true
|
{
|
||||||
|
if (!base.enabled) return
|
||||||
|
base.expanded = !base.expanded
|
||||||
|
}
|
||||||
|
hoverEnabled: base.enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle
|
Rectangle
|
||||||
|
|
|
@ -18,7 +18,7 @@ import UM 1.3 as UM
|
||||||
Item
|
Item
|
||||||
{
|
{
|
||||||
// The buildplate name
|
// The buildplate name
|
||||||
property alias buildplate: buildplateLabel.text
|
property var buildplate: null
|
||||||
|
|
||||||
// Height is one 18px label/icon
|
// Height is one 18px label/icon
|
||||||
height: 18 * screenScaleFactor // TODO: Theme!
|
height: 18 * screenScaleFactor // TODO: Theme!
|
||||||
|
@ -34,7 +34,16 @@ Item
|
||||||
Item
|
Item
|
||||||
{
|
{
|
||||||
height: parent.height
|
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
|
UM.RecolorImage
|
||||||
{
|
{
|
||||||
|
@ -44,6 +53,7 @@ Item
|
||||||
height: parent.height
|
height: parent.height
|
||||||
source: "../svg/icons/buildplate.svg"
|
source: "../svg/icons/buildplate.svg"
|
||||||
width: height
|
width: height
|
||||||
|
visible: buildplate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +63,8 @@ Item
|
||||||
color: "#191919" // TODO: Theme!
|
color: "#191919" // TODO: Theme!
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
font: UM.Theme.getFont("default") // 12pt, regular
|
font: UM.Theme.getFont("default") // 12pt, regular
|
||||||
text: ""
|
text: buildplate ? buildplate : ""
|
||||||
|
visible: text !== ""
|
||||||
|
|
||||||
// FIXED-LINE-HEIGHT:
|
// FIXED-LINE-HEIGHT:
|
||||||
height: 18 * screenScaleFactor // TODO: Theme!
|
height: 18 * screenScaleFactor // TODO: Theme!
|
||||||
|
|
|
@ -14,7 +14,12 @@ Item
|
||||||
property var tileWidth: 834 * screenScaleFactor // TODO: Theme!
|
property var tileWidth: 834 * screenScaleFactor // TODO: Theme!
|
||||||
property var tileHeight: 216 * screenScaleFactor // TODO: Theme!
|
property var tileHeight: 216 * screenScaleFactor // TODO: Theme!
|
||||||
property var tileSpacing: 60 * 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
|
height: centerSection.height
|
||||||
width: maximumWidth
|
width: maximumWidth
|
||||||
|
@ -129,7 +134,7 @@ Item
|
||||||
|
|
||||||
Repeater
|
Repeater
|
||||||
{
|
{
|
||||||
model: OutputDevice.printers
|
model: printers
|
||||||
MonitorPrinterCard
|
MonitorPrinterCard
|
||||||
{
|
{
|
||||||
printer: modelData
|
printer: modelData
|
||||||
|
@ -151,7 +156,7 @@ Item
|
||||||
width: 36 * screenScaleFactor // TODO: Theme!
|
width: 36 * screenScaleFactor // TODO: Theme!
|
||||||
height: 72 * screenScaleFactor // TODO: Theme!
|
height: 72 * screenScaleFactor // TODO: Theme!
|
||||||
z: 10
|
z: 10
|
||||||
visible: currentIndex < OutputDevice.printers.length - 1
|
visible: currentIndex < printers.length - 1
|
||||||
onClicked: navigateTo(currentIndex + 1)
|
onClicked: navigateTo(currentIndex + 1)
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
background: Rectangle
|
background: Rectangle
|
||||||
|
@ -225,9 +230,10 @@ Item
|
||||||
topMargin: 36 * screenScaleFactor // TODO: Theme!
|
topMargin: 36 * screenScaleFactor // TODO: Theme!
|
||||||
}
|
}
|
||||||
spacing: 8 * screenScaleFactor // TODO: Theme!
|
spacing: 8 * screenScaleFactor // TODO: Theme!
|
||||||
|
visible: printers.length > 1
|
||||||
Repeater
|
Repeater
|
||||||
{
|
{
|
||||||
model: OutputDevice.printers
|
model: printers
|
||||||
Button
|
Button
|
||||||
{
|
{
|
||||||
background: Rectangle
|
background: Rectangle
|
||||||
|
@ -243,7 +249,7 @@ Item
|
||||||
}
|
}
|
||||||
|
|
||||||
function navigateTo( i ) {
|
function navigateTo( i ) {
|
||||||
if (i >= 0 && i < OutputDevice.printers.length)
|
if (i >= 0 && i < printers.length)
|
||||||
{
|
{
|
||||||
tiles.x = -1 * i * (tileWidth + tileSpacing)
|
tiles.x = -1 * i * (tileWidth + tileSpacing)
|
||||||
currentIndex = i
|
currentIndex = i
|
||||||
|
|
|
@ -54,7 +54,7 @@ UM.Dialog
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
text:
|
text:
|
||||||
{
|
{
|
||||||
if (!printer.activePrintJob)
|
if (!printer || !printer.activePrintJob)
|
||||||
{
|
{
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,38 +39,62 @@ Item
|
||||||
color: "#eeeeee" // TODO: Theme!
|
color: "#eeeeee" // TODO: Theme!
|
||||||
position: 0
|
position: 0
|
||||||
}
|
}
|
||||||
Label
|
|
||||||
|
Rectangle
|
||||||
{
|
{
|
||||||
id: materialLabel
|
id: materialLabelWrapper
|
||||||
anchors
|
anchors
|
||||||
{
|
{
|
||||||
left: extruderIcon.right
|
left: extruderIcon.right
|
||||||
leftMargin: 12 * screenScaleFactor // TODO: Theme!
|
leftMargin: 12 * screenScaleFactor // TODO: Theme!
|
||||||
}
|
}
|
||||||
color: "#191919" // TODO: Theme!
|
color: materialLabel.visible > 0 ? "transparent" : "#eeeeee" // TODO: Theme!
|
||||||
elide: Text.ElideRight
|
|
||||||
font: UM.Theme.getFont("default") // 12pt, regular
|
|
||||||
text: ""
|
|
||||||
|
|
||||||
// FIXED-LINE-HEIGHT:
|
|
||||||
height: 18 * screenScaleFactor // TODO: Theme!
|
height: 18 * screenScaleFactor // TODO: Theme!
|
||||||
verticalAlignment: Text.AlignVCenter
|
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: parent.height
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Label
|
|
||||||
|
Rectangle
|
||||||
{
|
{
|
||||||
id: printCoreLabel
|
id: printCoreLabelWrapper
|
||||||
anchors
|
anchors
|
||||||
{
|
{
|
||||||
left: materialLabel.left
|
left: materialLabelWrapper.left
|
||||||
bottom: parent.bottom
|
bottom: parent.bottom
|
||||||
}
|
}
|
||||||
color: "#191919" // TODO: Theme!
|
color: printCoreLabel.visible > 0 ? "transparent" : "#eeeeee" // TODO: Theme!
|
||||||
elide: Text.ElideRight
|
|
||||||
font: UM.Theme.getFont("default_bold") // 12pt, bold
|
|
||||||
text: ""
|
|
||||||
|
|
||||||
// FIXED-LINE-HEIGHT:
|
|
||||||
height: 18 * screenScaleFactor // TODO: Theme!
|
height: 18 * screenScaleFactor // TODO: Theme!
|
||||||
verticalAlignment: Text.AlignVCenter
|
width: Math.max(printCoreLabel.contentWidth, 36 * screenScaleFactor) // TODO: Theme!
|
||||||
|
radius: 2 * screenScaleFactor // TODO: Theme!
|
||||||
|
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
id: printCoreLabel
|
||||||
|
|
||||||
|
color: "#191919" // TODO: Theme!
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font: UM.Theme.getFont("default_bold") // 12pt, bold
|
||||||
|
text: ""
|
||||||
|
visible: text !== ""
|
||||||
|
|
||||||
|
// FIXED-LINE-HEIGHT:
|
||||||
|
height: parent.height
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -56,5 +56,6 @@ Item
|
||||||
x: Math.round(size * 0.25) * screenScaleFactor
|
x: Math.round(size * 0.25) * screenScaleFactor
|
||||||
y: Math.round(size * 0.15625) * screenScaleFactor
|
y: Math.round(size * 0.15625) * screenScaleFactor
|
||||||
// TODO: Once 'size' is themed, screenScaleFactor won't be needed
|
// TODO: Once 'size' is themed, screenScaleFactor won't be needed
|
||||||
|
visible: position >= 0
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -26,6 +26,7 @@ Item
|
||||||
|
|
||||||
ExpandableCard
|
ExpandableCard
|
||||||
{
|
{
|
||||||
|
enabled: printJob != null
|
||||||
borderColor: printJob.configurationChanges.length !== 0 ? "#f5a623" : "#CCCCCC" // TODO: Theme!
|
borderColor: printJob.configurationChanges.length !== 0 ? "#f5a623" : "#CCCCCC" // TODO: Theme!
|
||||||
headerItem: Row
|
headerItem: Row
|
||||||
{
|
{
|
||||||
|
@ -41,32 +42,56 @@ Item
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
Label
|
Item
|
||||||
{
|
{
|
||||||
text: printJob && printJob.name ? printJob.name : ""
|
|
||||||
color: "#374355"
|
|
||||||
elide: Text.ElideRight
|
|
||||||
font: UM.Theme.getFont("medium") // 14pt, regular
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
width: 216 * screenScaleFactor // TODO: Theme! (Should match column size)
|
|
||||||
|
|
||||||
// FIXED-LINE-HEIGHT:
|
|
||||||
height: 18 * screenScaleFactor // TODO: Theme!
|
height: 18 * screenScaleFactor // TODO: Theme!
|
||||||
verticalAlignment: Text.AlignVCenter
|
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
|
||||||
|
visible: printJob
|
||||||
|
|
||||||
|
// FIXED-LINE-HEIGHT:
|
||||||
|
height: parent.height
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
// FIXED-LINE-HEIGHT:
|
Item
|
||||||
|
{
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
height: 18 * screenScaleFactor // TODO: Theme!
|
height: 18 * screenScaleFactor // TODO: Theme!
|
||||||
verticalAlignment: Text.AlignVCenter
|
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
|
||||||
|
visible: printJob
|
||||||
|
|
||||||
|
// FIXED-LINE-HEIGHT:
|
||||||
|
height: 18 * screenScaleFactor // TODO: Theme!
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item
|
Item
|
||||||
|
@ -75,6 +100,14 @@ Item
|
||||||
height: 18 * screenScaleFactor // TODO: This should be childrenRect.height but QML throws warnings
|
height: 18 * screenScaleFactor // TODO: This should be childrenRect.height but QML throws warnings
|
||||||
width: childrenRect.width
|
width: childrenRect.width
|
||||||
|
|
||||||
|
Rectangle
|
||||||
|
{
|
||||||
|
color: "#eeeeee"
|
||||||
|
width: 72 * screenScaleFactor // TODO: Theme!
|
||||||
|
height: parent.height
|
||||||
|
visible: !printJob
|
||||||
|
}
|
||||||
|
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
id: printerAssignmentLabel
|
id: printerAssignmentLabel
|
||||||
|
@ -100,7 +133,7 @@ Item
|
||||||
width: 120 * screenScaleFactor // TODO: Theme!
|
width: 120 * screenScaleFactor // TODO: Theme!
|
||||||
|
|
||||||
// FIXED-LINE-HEIGHT:
|
// FIXED-LINE-HEIGHT:
|
||||||
height: 18 * screenScaleFactor // TODO: Theme!
|
height: parent.height
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,6 +148,7 @@ Item
|
||||||
}
|
}
|
||||||
height: childrenRect.height
|
height: childrenRect.height
|
||||||
spacing: 6 // TODO: Theme!
|
spacing: 6 // TODO: Theme!
|
||||||
|
visible: printJob
|
||||||
|
|
||||||
Repeater
|
Repeater
|
||||||
{
|
{
|
||||||
|
|
|
@ -16,23 +16,28 @@ Item
|
||||||
width: size
|
width: size
|
||||||
height: size
|
height: size
|
||||||
|
|
||||||
// Actual content
|
Rectangle
|
||||||
Image
|
|
||||||
{
|
{
|
||||||
id: previewImage
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
opacity:
|
color: printJob ? "transparent" : "#eeeeee" // TODO: Theme!
|
||||||
|
radius: 8 // TODO: Theme!
|
||||||
|
Image
|
||||||
{
|
{
|
||||||
if (printJob && (printJob.state == "error" || printJob.configurationChanges.length > 0 || !printJob.isActive))
|
id: previewImage
|
||||||
|
anchors.fill: parent
|
||||||
|
opacity:
|
||||||
{
|
{
|
||||||
return 0.5
|
if (printJob && (printJob.state == "error" || printJob.configurationChanges.length > 0 || !printJob.isActive))
|
||||||
|
{
|
||||||
|
return 0.5
|
||||||
|
}
|
||||||
|
return 1.0
|
||||||
}
|
}
|
||||||
return 1.0
|
source: printJob ? printJob.previewImageUrl : ""
|
||||||
}
|
}
|
||||||
source: printJob ? printJob.previewImageUrl : ""
|
|
||||||
visible: printJob
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
UM.RecolorImage
|
UM.RecolorImage
|
||||||
{
|
{
|
||||||
id: ultiBotImage
|
id: ultiBotImage
|
||||||
|
|
|
@ -34,16 +34,16 @@ Item
|
||||||
{
|
{
|
||||||
background: Rectangle
|
background: Rectangle
|
||||||
{
|
{
|
||||||
color: printJob && printJob.isActive ? "#e4e4f2" : "#f3f3f9" // TODO: Theme!
|
color: "#f5f5f5" // TODO: Theme!
|
||||||
implicitHeight: visible ? 8 * screenScaleFactor : 0 // TODO: Theme!
|
implicitHeight: visible ? 8 * screenScaleFactor : 0 // TODO: Theme!
|
||||||
implicitWidth: 180 * screenScaleFactor // TODO: Theme!
|
implicitWidth: 180 * screenScaleFactor // TODO: Theme!
|
||||||
radius: 4 * screenScaleFactor // TODO: Theme!
|
radius: 2 * screenScaleFactor // TODO: Theme!
|
||||||
}
|
}
|
||||||
progress: Rectangle
|
progress: Rectangle
|
||||||
{
|
{
|
||||||
id: progressItem;
|
id: progressItem;
|
||||||
color: printJob && printJob.isActive ? "#0a0850" : "#9392b2" // TODO: Theme!
|
color: printJob && printJob.isActive ? "#3282ff" : "#CCCCCC" // TODO: Theme!
|
||||||
radius: 4 * screenScaleFactor // TODO: Theme!
|
radius: 2 * screenScaleFactor // TODO: Theme!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,16 +33,24 @@ Item
|
||||||
width: 834 * screenScaleFactor // TODO: Theme!
|
width: 834 * screenScaleFactor // TODO: Theme!
|
||||||
height: childrenRect.height
|
height: childrenRect.height
|
||||||
|
|
||||||
// Printer portion
|
|
||||||
Rectangle
|
Rectangle
|
||||||
{
|
{
|
||||||
id: printerInfo
|
id: background
|
||||||
|
anchors.fill: parent
|
||||||
|
color: "#FFFFFF" // TODO: Theme!
|
||||||
border
|
border
|
||||||
{
|
{
|
||||||
color: "#CCCCCC" // TODO: Theme!
|
color: "#CCCCCC" // TODO: Theme!
|
||||||
width: borderSize // TODO: Remove once themed
|
width: borderSize // TODO: Remove once themed
|
||||||
}
|
}
|
||||||
color: "white" // TODO: Theme!
|
radius: 2 * screenScaleFactor // TODO: Theme!
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printer portion
|
||||||
|
Item
|
||||||
|
{
|
||||||
|
id: printerInfo
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 144 * screenScaleFactor // TODO: Theme!
|
height: 144 * screenScaleFactor // TODO: Theme!
|
||||||
|
|
||||||
|
@ -56,15 +64,22 @@ Item
|
||||||
}
|
}
|
||||||
spacing: 18 * screenScaleFactor // TODO: Theme!
|
spacing: 18 * screenScaleFactor // TODO: Theme!
|
||||||
|
|
||||||
Image
|
Rectangle
|
||||||
{
|
{
|
||||||
id: printerImage
|
id: printerImage
|
||||||
width: 108 * screenScaleFactor // TODO: Theme!
|
width: 108 * screenScaleFactor // TODO: Theme!
|
||||||
height: 108 * screenScaleFactor // TODO: Theme!
|
height: 108 * screenScaleFactor // TODO: Theme!
|
||||||
fillMode: Image.PreserveAspectFit
|
color: printer ? "transparent" : "#eeeeee" // TODO: Theme!
|
||||||
source: "../png/" + printer.type + ".png"
|
radius: 8 // TODO: Theme!
|
||||||
mipmap: true
|
Image
|
||||||
|
{
|
||||||
|
anchors.fill: parent
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
source: printer ? "../png/" + printer.type + ".png" : ""
|
||||||
|
mipmap: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Item
|
Item
|
||||||
{
|
{
|
||||||
|
@ -75,20 +90,38 @@ Item
|
||||||
width: 180 * screenScaleFactor // TODO: Theme!
|
width: 180 * screenScaleFactor // TODO: Theme!
|
||||||
height: printerNameLabel.height + printerFamilyPill.height + 6 * screenScaleFactor // TODO: Theme!
|
height: printerNameLabel.height + printerFamilyPill.height + 6 * screenScaleFactor // TODO: Theme!
|
||||||
|
|
||||||
Label
|
Rectangle
|
||||||
{
|
{
|
||||||
id: printerNameLabel
|
id: printerNameLabel
|
||||||
text: printer && printer.name ? printer.name : ""
|
// color: "#414054" // TODO: Theme!
|
||||||
color: "#414054" // TODO: Theme!
|
color: printer ? "transparent" : "#eeeeee" // TODO: Theme!
|
||||||
elide: Text.ElideRight
|
|
||||||
font: UM.Theme.getFont("large_bold") // 16pt, bold
|
|
||||||
width: parent.width
|
|
||||||
|
|
||||||
// FIXED-LINE-HEIGHT:
|
|
||||||
height: 18 * screenScaleFactor // TODO: Theme!
|
height: 18 * screenScaleFactor // TODO: Theme!
|
||||||
verticalAlignment: Text.AlignVCenter
|
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") // 16pt, bold
|
||||||
|
width: parent.width
|
||||||
|
visible: printer
|
||||||
|
|
||||||
|
// FIXED-LINE-HEIGHT:
|
||||||
|
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
|
MonitorPrinterPill
|
||||||
{
|
{
|
||||||
id: printerFamilyPill
|
id: printerFamilyPill
|
||||||
|
@ -98,7 +131,7 @@ Item
|
||||||
topMargin: 6 * screenScaleFactor // TODO: Theme!
|
topMargin: 6 * screenScaleFactor // TODO: Theme!
|
||||||
left: printerNameLabel.left
|
left: printerNameLabel.left
|
||||||
}
|
}
|
||||||
text: printer.type
|
text: printer ? printer.type : ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,16 +139,30 @@ Item
|
||||||
{
|
{
|
||||||
id: printerConfiguration
|
id: printerConfiguration
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
buildplate: "Glass"
|
buildplate: printer ? "Glass" : null // 'Glass' as a default
|
||||||
configurations:
|
configurations:
|
||||||
[
|
{
|
||||||
base.printer.printerConfiguration.extruderConfigurations[0],
|
var configs = []
|
||||||
base.printer.printerConfiguration.extruderConfigurations[1]
|
if (printer)
|
||||||
]
|
{
|
||||||
height: 72 * screenScaleFactor // TODO: Theme!
|
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
|
PrintJobContextMenu
|
||||||
{
|
{
|
||||||
id: contextButton
|
id: contextButton
|
||||||
|
@ -126,10 +173,11 @@ Item
|
||||||
top: parent.top
|
top: parent.top
|
||||||
topMargin: 12 * screenScaleFactor // TODO: Theme!
|
topMargin: 12 * screenScaleFactor // TODO: Theme!
|
||||||
}
|
}
|
||||||
printJob: printer.activePrintJob
|
printJob: printer ? printer.activePrintJob : null
|
||||||
width: 36 * screenScaleFactor // TODO: Theme!
|
width: 36 * screenScaleFactor // TODO: Theme!
|
||||||
height: 36 * screenScaleFactor // TODO: Theme!
|
height: 36 * screenScaleFactor // TODO: Theme!
|
||||||
enabled: base.enabled
|
enabled: base.enabled
|
||||||
|
visible: printer
|
||||||
}
|
}
|
||||||
CameraButton
|
CameraButton
|
||||||
{
|
{
|
||||||
|
@ -143,10 +191,24 @@ Item
|
||||||
}
|
}
|
||||||
iconSource: "../svg/icons/camera.svg"
|
iconSource: "../svg/icons/camera.svg"
|
||||||
enabled: base.enabled
|
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
|
// Print job portion
|
||||||
Rectangle
|
Rectangle
|
||||||
{
|
{
|
||||||
|
@ -158,10 +220,10 @@ Item
|
||||||
}
|
}
|
||||||
border
|
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
|
width: borderSize // TODO: Remove once themed
|
||||||
}
|
}
|
||||||
color: "white" // TODO: Theme!
|
color: "transparent" // TODO: Theme!
|
||||||
height: 84 * screenScaleFactor + borderSize // TODO: Remove once themed
|
height: 84 * screenScaleFactor + borderSize // TODO: Remove once themed
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
|
||||||
|
@ -184,9 +246,12 @@ Item
|
||||||
{
|
{
|
||||||
verticalCenter: parent.verticalCenter
|
verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
color: "#414054" // TODO: Theme!
|
color: printer ? "#414054" : "#aaaaaa" // TODO: Theme!
|
||||||
font: UM.Theme.getFont("large_bold") // 16pt, bold
|
font: UM.Theme.getFont("large_bold") // 16pt, bold
|
||||||
text: {
|
text: {
|
||||||
|
if (!printer) {
|
||||||
|
return catalog.i18nc("@label:status", "Loading...")
|
||||||
|
}
|
||||||
if (printer && printer.state == "disabled")
|
if (printer && printer.state == "disabled")
|
||||||
{
|
{
|
||||||
return catalog.i18nc("@label:status", "Unavailable")
|
return catalog.i18nc("@label:status", "Unavailable")
|
||||||
|
@ -215,10 +280,10 @@ Item
|
||||||
MonitorPrintJobPreview
|
MonitorPrintJobPreview
|
||||||
{
|
{
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
printJob: base.printer.activePrintJob
|
printJob: printer ? printer.activePrintJob : null
|
||||||
size: parent.height
|
size: parent.height
|
||||||
}
|
}
|
||||||
visible: printer.activePrintJob
|
visible: printer && printer.activePrintJob && !printerStatus.visible
|
||||||
}
|
}
|
||||||
|
|
||||||
Item
|
Item
|
||||||
|
@ -229,15 +294,15 @@ Item
|
||||||
}
|
}
|
||||||
width: 180 * screenScaleFactor // TODO: Theme!
|
width: 180 * screenScaleFactor // TODO: Theme!
|
||||||
height: printerNameLabel.height + printerFamilyPill.height + 6 * screenScaleFactor // TODO: Theme!
|
height: printerNameLabel.height + printerFamilyPill.height + 6 * screenScaleFactor // TODO: Theme!
|
||||||
visible: printer.activePrintJob
|
visible: printer && printer.activePrintJob && !printerStatus.visible
|
||||||
|
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
id: printerJobNameLabel
|
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
|
elide: Text.ElideRight
|
||||||
font: UM.Theme.getFont("large_bold") // 16pt, bold
|
font: UM.Theme.getFont("large") // 16pt, bold
|
||||||
text: base.printer.activePrintJob ? base.printer.activePrintJob.name : "Untitled" // TODO: I18N
|
text: printer && printer.activePrintJob ? printer.activePrintJob.name : "Untitled" // TODO: I18N
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
|
||||||
// FIXED-LINE-HEIGHT:
|
// FIXED-LINE-HEIGHT:
|
||||||
|
@ -254,10 +319,10 @@ Item
|
||||||
topMargin: 6 * screenScaleFactor // TODO: Theme!
|
topMargin: 6 * screenScaleFactor // TODO: Theme!
|
||||||
left: printerJobNameLabel.left
|
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
|
elide: Text.ElideRight
|
||||||
font: UM.Theme.getFont("default") // 12pt, regular
|
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
|
width: parent.width
|
||||||
|
|
||||||
// FIXED-LINE-HEIGHT:
|
// FIXED-LINE-HEIGHT:
|
||||||
|
@ -272,8 +337,8 @@ Item
|
||||||
{
|
{
|
||||||
verticalCenter: parent.verticalCenter
|
verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
printJob: printer.activePrintJob
|
printJob: printer && printer.activePrintJob
|
||||||
visible: printer.activePrintJob && printer.activePrintJob.configurationChanges.length === 0
|
visible: printer && printer.activePrintJob && printer.activePrintJob.configurationChanges.length === 0 && !printerStatus.visible
|
||||||
}
|
}
|
||||||
|
|
||||||
Label
|
Label
|
||||||
|
@ -284,7 +349,7 @@ Item
|
||||||
}
|
}
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
text: "Requires configuration changes"
|
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:
|
// FIXED-LINE-HEIGHT:
|
||||||
height: 18 * screenScaleFactor // TODO: Theme!
|
height: 18 * screenScaleFactor // TODO: Theme!
|
||||||
|
@ -326,7 +391,7 @@ Item
|
||||||
}
|
}
|
||||||
implicitHeight: 32 * screenScaleFactor // TODO: Theme!
|
implicitHeight: 32 * screenScaleFactor // TODO: Theme!
|
||||||
implicitWidth: 96 * 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() : {}
|
onClicked: base.enabled ? overrideConfirmationDialog.open() : {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ Item
|
||||||
property alias buildplate: buildplateConfig.buildplate
|
property alias buildplate: buildplateConfig.buildplate
|
||||||
|
|
||||||
// Array of extracted extruder configurations
|
// Array of extracted extruder configurations
|
||||||
property var configurations: null
|
property var configurations: [null,null]
|
||||||
|
|
||||||
// Default size, but should be stretched to fill parent
|
// Default size, but should be stretched to fill parent
|
||||||
height: 72 * parent.height
|
height: 72 * parent.height
|
||||||
|
@ -37,10 +37,10 @@ Item
|
||||||
|
|
||||||
MonitorExtruderConfiguration
|
MonitorExtruderConfiguration
|
||||||
{
|
{
|
||||||
color: modelData.activeMaterial ? modelData.activeMaterial.color : "#eeeeee" // TODO: Theme!
|
color: modelData && modelData.activeMaterial ? modelData.activeMaterial.color : "#eeeeee" // TODO: Theme!
|
||||||
material: modelData.activeMaterial ? modelData.activeMaterial.name : ""
|
material: modelData && modelData.activeMaterial ? modelData.activeMaterial.name : ""
|
||||||
position: modelData.position
|
position: modelData && typeof(modelData.position) === "number" ? modelData.position : -1 // Use negative one to create empty extruder number
|
||||||
printCore: modelData.hotendID
|
printCore: modelData ? modelData.hotendID : ""
|
||||||
|
|
||||||
// Keep things responsive!
|
// Keep things responsive!
|
||||||
width: Math.floor((base.width - (configurations.length - 1) * extruderConfigurationRow.spacing) / configurations.length)
|
width: Math.floor((base.width - (configurations.length - 1) * extruderConfigurationRow.spacing) / configurations.length)
|
||||||
|
@ -53,6 +53,6 @@ Item
|
||||||
{
|
{
|
||||||
id: buildplateConfig
|
id: buildplateConfig
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
buildplate: "Glass" // 'Glass' as a default
|
buildplate: null
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -27,12 +27,12 @@ Item
|
||||||
}
|
}
|
||||||
|
|
||||||
implicitHeight: 18 * screenScaleFactor // TODO: Theme!
|
implicitHeight: 18 * screenScaleFactor // TODO: Theme!
|
||||||
implicitWidth: printerNameLabel.contentWidth + 12 // TODO: Theme!
|
implicitWidth: Math.max(printerNameLabel.contentWidth + 12 * screenScaleFactor, 36 * screenScaleFactor) // TODO: Theme!
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: background
|
id: background
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: "#e4e4f2" // TODO: Theme!
|
color: printerNameLabel.visible ? "#e4e4f2" : "#eeeeee"// TODO: Theme!
|
||||||
radius: 2 * screenScaleFactor // TODO: Theme!
|
radius: 2 * screenScaleFactor // TODO: Theme!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,6 +41,7 @@ Item
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
color: "#535369" // TODO: Theme!
|
color: "#535369" // TODO: Theme!
|
||||||
text: tagText
|
text: tagText
|
||||||
font.pointSize: 10
|
font.pointSize: 10 // TODO: Theme!
|
||||||
|
visible: text !== ""
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -42,8 +42,8 @@ Item
|
||||||
{
|
{
|
||||||
id: externalLinkIcon
|
id: externalLinkIcon
|
||||||
anchors.verticalCenter: manageQueueLabel.verticalCenter
|
anchors.verticalCenter: manageQueueLabel.verticalCenter
|
||||||
color: UM.Theme.getColor("primary")
|
color: UM.Theme.getColor("text_link")
|
||||||
source: "../svg/icons/external_link.svg"
|
source: UM.Theme.getIcon("external_link")
|
||||||
width: 16 * screenScaleFactor // TODO: Theme! (Y U NO USE 18 LIKE ALL OTHER ICONS?!)
|
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?!)
|
height: 16 * screenScaleFactor // TODO: Theme! (Y U NO USE 18 LIKE ALL OTHER ICONS?!)
|
||||||
}
|
}
|
||||||
|
@ -56,10 +56,11 @@ Item
|
||||||
leftMargin: 6 * screenScaleFactor // TODO: Theme!
|
leftMargin: 6 * screenScaleFactor // TODO: Theme!
|
||||||
verticalCenter: externalLinkIcon.verticalCenter
|
verticalCenter: externalLinkIcon.verticalCenter
|
||||||
}
|
}
|
||||||
color: UM.Theme.getColor("primary")
|
color: UM.Theme.getColor("text_link")
|
||||||
font: UM.Theme.getFont("default") // 12pt, regular
|
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")
|
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!
|
topMargin: 12 * screenScaleFactor // TODO: Theme!
|
||||||
}
|
}
|
||||||
style: UM.Theme.styles.scrollview
|
style: UM.Theme.styles.scrollview
|
||||||
visible: OutputDevice.receivedPrintJobs
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
|
||||||
ListView
|
ListView
|
||||||
|
@ -160,7 +160,7 @@ Item
|
||||||
}
|
}
|
||||||
printJob: modelData
|
printJob: modelData
|
||||||
}
|
}
|
||||||
model: OutputDevice.queuedPrintJobs
|
model: OutputDevice.receivedPrintJobs ? OutputDevice.queuedPrintJobs : [null,null]
|
||||||
spacing: 6 // TODO: Theme!
|
spacing: 6 // TODO: Theme!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,8 +64,10 @@ Component
|
||||||
}
|
}
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 264 * screenScaleFactor // TODO: Theme!
|
height: 264 * screenScaleFactor // TODO: Theme!
|
||||||
MonitorCarousel {
|
MonitorCarousel
|
||||||
|
{
|
||||||
id: carousel
|
id: carousel
|
||||||
|
printers: OutputDevice.receivedPrintJobs ? OutputDevice.printers : [null]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -90,67 +90,4 @@ Item {
|
||||||
source: "DiscoverUM3Action.qml";
|
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
|
# Create extra components
|
||||||
CuraApplication.getInstance().addAdditionalComponent("monitorButtons", self.__additional_components_view.findChild(QObject, "networkPrinterConnectButton"))
|
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.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import urllib.parse
|
|
||||||
from typing import Dict, TYPE_CHECKING, Set
|
from typing import Dict, TYPE_CHECKING, Set
|
||||||
|
|
||||||
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
|
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
|
||||||
|
@ -10,9 +9,7 @@ from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from UM.Job import Job
|
from UM.Job import Job
|
||||||
from UM.Logger import Logger
|
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
|
# Absolute imports don't work in plugins
|
||||||
from .Models import ClusterMaterial, LocalMaterial
|
from .Models import ClusterMaterial, LocalMaterial
|
||||||
|
|
||||||
|
@ -37,7 +34,6 @@ class SendMaterialJob(Job):
|
||||||
#
|
#
|
||||||
# \param reply The reply from the printer, a json file.
|
# \param reply The reply from the printer, a json file.
|
||||||
def _onGetRemoteMaterials(self, reply: QNetworkReply) -> None:
|
def _onGetRemoteMaterials(self, reply: QNetworkReply) -> None:
|
||||||
|
|
||||||
# Got an error from the HTTP request. If we did not receive a 200 something happened.
|
# Got an error from the HTTP request. If we did not receive a 200 something happened.
|
||||||
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
|
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
|
||||||
Logger.log("e", "Error fetching materials from printer: %s", reply.errorString())
|
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.
|
# \param remote_materials_by_guid The remote materials by GUID.
|
||||||
def _sendMissingMaterials(self, remote_materials_by_guid: Dict[str, ClusterMaterial]) -> None:
|
def _sendMissingMaterials(self, remote_materials_by_guid: Dict[str, ClusterMaterial]) -> None:
|
||||||
|
|
||||||
# Collect local materials
|
# Collect local materials
|
||||||
local_materials_by_guid = self._getLocalMaterials()
|
local_materials_by_guid = self._getLocalMaterials()
|
||||||
if len(local_materials_by_guid) == 0:
|
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.
|
# \param materials_to_send A set with id's of materials that must be sent.
|
||||||
def _sendMaterials(self, materials_to_send: Set[str]) -> None:
|
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 root_material_id in material_group_dict:
|
||||||
for file_path in file_paths:
|
if root_material_id not in materials_to_send:
|
||||||
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:
|
|
||||||
# If the material does not have to be sent we skip it.
|
# If the material does not have to be sent we skip it.
|
||||||
continue
|
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.
|
## 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 file_name The name of the material file.
|
||||||
# \param material_id The ID of the material in the 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:
|
def _sendMaterialFile(self, file_path: str, file_name: str, material_id: str) -> None:
|
||||||
|
|
||||||
parts = []
|
parts = []
|
||||||
|
|
||||||
# Add the material file.
|
# Add the material file.
|
||||||
|
@ -171,27 +165,31 @@ class SendMaterialJob(Job):
|
||||||
# \return a dictionary of LocalMaterial objects by GUID
|
# \return a dictionary of LocalMaterial objects by GUID
|
||||||
def _getLocalMaterials(self) -> Dict[str, LocalMaterial]:
|
def _getLocalMaterials(self) -> Dict[str, LocalMaterial]:
|
||||||
result = {} # type: Dict[str, LocalMaterial]
|
result = {} # type: Dict[str, LocalMaterial]
|
||||||
container_registry = Application.getInstance().getContainerRegistry()
|
material_manager = Application.getInstance().getMaterialManager()
|
||||||
material_containers = container_registry.findContainersMetadata(type = "material")
|
|
||||||
|
material_group_dict = material_manager.getAllMaterialGroups()
|
||||||
|
|
||||||
# Find the latest version of all material containers in the registry.
|
# 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:
|
try:
|
||||||
# material version must be an int
|
# material version must be an int
|
||||||
material["version"] = int(material["version"])
|
material_metadata["version"] = int(material_metadata["version"])
|
||||||
|
|
||||||
# Create a new local material
|
# 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 \
|
if local_material.GUID not in result or \
|
||||||
local_material.version > result.get(local_material.GUID).version:
|
local_material.version > result.get(local_material.GUID).version:
|
||||||
result[local_material.GUID] = local_material
|
result[local_material.GUID] = local_material
|
||||||
|
|
||||||
except KeyError:
|
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:
|
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:
|
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
|
return result
|
||||||
|
|
|
@ -114,6 +114,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
||||||
if key == um_network_key:
|
if key == um_network_key:
|
||||||
if not self._discovered_devices[key].isConnected():
|
if not self._discovered_devices[key].isConnected():
|
||||||
Logger.log("d", "Attempting to connect with [%s]" % key)
|
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].connect()
|
||||||
self._discovered_devices[key].connectionStateChanged.connect(self._onDeviceConnectionStateChanged)
|
self._discovered_devices[key].connectionStateChanged.connect(self._onDeviceConnectionStateChanged)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -1,26 +1,29 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
import copy
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
from unittest import TestCase, mock
|
from unittest import TestCase, mock
|
||||||
from unittest.mock import patch, call
|
from unittest.mock import patch, call, MagicMock
|
||||||
|
|
||||||
from PyQt5.QtCore import QByteArray
|
from PyQt5.QtCore import QByteArray
|
||||||
|
|
||||||
from UM.MimeTypeDatabase import MimeType
|
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
|
|
||||||
|
from cura.Machines.MaterialGroup import MaterialGroup
|
||||||
|
from cura.Machines.MaterialNode import MaterialNode
|
||||||
|
|
||||||
from plugins.UM3NetworkPrinting.src.SendMaterialJob import SendMaterialJob
|
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("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):
|
class TestSendMaterialJob(TestCase):
|
||||||
|
# version 1
|
||||||
_LOCAL_MATERIAL_WHITE = {"type": "material", "status": "unknown", "id": "generic_pla_white",
|
_LOCAL_MATERIAL_WHITE = {"type": "material", "status": "unknown", "id": "generic_pla_white",
|
||||||
"base_file": "generic_pla_white", "setting_version": "5", "name": "White PLA",
|
"base_file": "generic_pla_white", "setting_version": "5", "name": "White PLA",
|
||||||
"brand": "Generic", "material": "PLA", "color_name": "White",
|
"brand": "Generic", "material": "PLA", "color_name": "White",
|
||||||
|
@ -29,6 +32,37 @@ class TestSendMaterialJob(TestCase):
|
||||||
"properties": {"density": "1.00", "diameter": "2.85", "weight": "750"},
|
"properties": {"density": "1.00", "diameter": "2.85", "weight": "750"},
|
||||||
"definition": "fdmprinter", "compatible": True}
|
"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",
|
_LOCAL_MATERIAL_BLACK = {"type": "material", "status": "unknown", "id": "generic_pla_black",
|
||||||
"base_file": "generic_pla_black", "setting_version": "5", "name": "Yellow CPE",
|
"base_file": "generic_pla_black", "setting_version": "5", "name": "Yellow CPE",
|
||||||
"brand": "Ultimaker", "material": "CPE", "color_name": "Black",
|
"brand": "Ultimaker", "material": "CPE", "color_name": "Black",
|
||||||
|
@ -37,6 +71,9 @@ class TestSendMaterialJob(TestCase):
|
||||||
"properties": {"density": "1.01", "diameter": "2.85", "weight": "750"},
|
"properties": {"density": "1.01", "diameter": "2.85", "weight": "750"},
|
||||||
"definition": "fdmprinter", "compatible": True}
|
"definition": "fdmprinter", "compatible": True}
|
||||||
|
|
||||||
|
_LOCAL_MATERIAL_BLACK_ALL_RESULT = {"generic_pla_black": MaterialGroup("generic_pla_black",
|
||||||
|
MaterialNode(_LOCAL_MATERIAL_BLACK))}
|
||||||
|
|
||||||
_REMOTE_MATERIAL_WHITE = {
|
_REMOTE_MATERIAL_WHITE = {
|
||||||
"guid": "badb0ee7-87c8-4f3f-9398-938587b67dce",
|
"guid": "badb0ee7-87c8-4f3f-9398-938587b67dce",
|
||||||
"material": "PLA",
|
"material": "PLA",
|
||||||
|
@ -55,14 +92,17 @@ class TestSendMaterialJob(TestCase):
|
||||||
"density": 1.00
|
"density": 1.00
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_run(self, device_mock, reply_mock):
|
def test_run(self):
|
||||||
|
device_mock = MagicMock()
|
||||||
job = SendMaterialJob(device_mock)
|
job = SendMaterialJob(device_mock)
|
||||||
job.run()
|
job.run()
|
||||||
|
|
||||||
# We expect the materials endpoint to be called when the job runs.
|
# We expect the materials endpoint to be called when the job runs.
|
||||||
device_mock.get.assert_called_with("materials/", on_finished = job._onGetRemoteMaterials)
|
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
|
reply_mock.attribute.return_value = 404
|
||||||
job = SendMaterialJob(device_mock)
|
job = SendMaterialJob(device_mock)
|
||||||
job._onGetRemoteMaterials(reply_mock)
|
job._onGetRemoteMaterials(reply_mock)
|
||||||
|
@ -70,7 +110,9 @@ class TestSendMaterialJob(TestCase):
|
||||||
# We expect the device not to be called for any follow up.
|
# We expect the device not to be called for any follow up.
|
||||||
self.assertEqual(0, device_mock.createFormPart.call_count)
|
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.attribute.return_value = 200
|
||||||
reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("cp500"))
|
reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("cp500"))
|
||||||
job = SendMaterialJob(device_mock)
|
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.
|
# 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)
|
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.attribute.return_value = 200
|
||||||
reply_mock.readAll.return_value = QByteArray(b"Six sick hicks nick six slick bricks with picks and sticks.")
|
reply_mock.readAll.return_value = QByteArray(b"Six sick hicks nick six slick bricks with picks and sticks.")
|
||||||
job = SendMaterialJob(device_mock)
|
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.
|
# 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)
|
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
|
reply_mock.attribute.return_value = 200
|
||||||
remote_material_without_guid = self._REMOTE_MATERIAL_WHITE.copy()
|
remote_material_without_guid = self._REMOTE_MATERIAL_WHITE.copy()
|
||||||
del remote_material_without_guid["guid"]
|
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.
|
# 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)
|
self.assertEqual(0, device_mock.createFormPart.call_count)
|
||||||
|
|
||||||
|
@patch("cura.Machines.MaterialManager.MaterialManager")
|
||||||
@patch("cura.Settings.CuraContainerRegistry")
|
@patch("cura.Settings.CuraContainerRegistry")
|
||||||
@patch("UM.Application")
|
@patch("UM.Application")
|
||||||
def test__onGetRemoteMaterials_withInvalidVersionInLocalMaterial(self, application_mock, container_registry_mock,
|
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.attribute.return_value = 200
|
||||||
reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("ascii"))
|
reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("ascii"))
|
||||||
|
|
||||||
localMaterialWhiteWithInvalidVersion = self._LOCAL_MATERIAL_WHITE.copy()
|
material_manager_mock.getAllMaterialGroups.return_value = self._LOCAL_MATERIAL_WHITE_INVALID_VERSION_ALL_RESULT.copy()
|
||||||
localMaterialWhiteWithInvalidVersion["version"] = "one"
|
|
||||||
container_registry_mock.findContainersMetadata.return_value = [localMaterialWhiteWithInvalidVersion]
|
|
||||||
|
|
||||||
application_mock.getContainerRegistry.return_value = container_registry_mock
|
|
||||||
|
|
||||||
with mock.patch.object(Application, "getInstance", new = lambda: application_mock):
|
with mock.patch.object(Application, "getInstance", new = lambda: application_mock):
|
||||||
job = SendMaterialJob(device_mock)
|
job = SendMaterialJob(device_mock)
|
||||||
|
@ -118,15 +166,16 @@ class TestSendMaterialJob(TestCase):
|
||||||
|
|
||||||
self.assertEqual(0, device_mock.createFormPart.call_count)
|
self.assertEqual(0, device_mock.createFormPart.call_count)
|
||||||
|
|
||||||
@patch("cura.Settings.CuraContainerRegistry")
|
@patch("UM.Application.Application.getInstance")
|
||||||
@patch("UM.Application")
|
def test__onGetRemoteMaterials_withNoUpdate(self, application_mock):
|
||||||
def test__onGetRemoteMaterials_withNoUpdate(self, application_mock, container_registry_mock, reply_mock,
|
reply_mock = MagicMock()
|
||||||
device_mock):
|
device_mock = MagicMock()
|
||||||
application_mock.getContainerRegistry.return_value = container_registry_mock
|
container_registry_mock = application_mock.getContainerRegistry.return_value
|
||||||
|
material_manager_mock = application_mock.getMaterialManager.return_value
|
||||||
|
|
||||||
device_mock.createFormPart.return_value = "_xXx_"
|
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.attribute.return_value = 200
|
||||||
reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("ascii"))
|
reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("ascii"))
|
||||||
|
@ -138,24 +187,25 @@ class TestSendMaterialJob(TestCase):
|
||||||
self.assertEqual(0, device_mock.createFormPart.call_count)
|
self.assertEqual(0, device_mock.createFormPart.call_count)
|
||||||
self.assertEqual(0, device_mock.postFormWithParts.call_count)
|
self.assertEqual(0, device_mock.postFormWithParts.call_count)
|
||||||
|
|
||||||
@patch("cura.Settings.CuraContainerRegistry")
|
@patch("UM.Application.Application.getInstance")
|
||||||
@patch("UM.Application")
|
def test__onGetRemoteMaterials_withUpdatedMaterial(self, get_instance_mock):
|
||||||
def test__onGetRemoteMaterials_withUpdatedMaterial(self, application_mock, container_registry_mock, reply_mock,
|
reply_mock = MagicMock()
|
||||||
device_mock):
|
device_mock = MagicMock()
|
||||||
application_mock.getContainerRegistry.return_value = container_registry_mock
|
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_"
|
device_mock.createFormPart.return_value = "_xXx_"
|
||||||
|
|
||||||
localMaterialWhiteWithHigherVersion = self._LOCAL_MATERIAL_WHITE.copy()
|
material_manager_mock.getAllMaterialGroups.return_value = self._LOCAL_MATERIAL_WHITE_NEWER_ALL_RESULT.copy()
|
||||||
localMaterialWhiteWithHigherVersion["version"] = "2"
|
|
||||||
container_registry_mock.findContainersMetadata.return_value = [localMaterialWhiteWithHigherVersion]
|
|
||||||
|
|
||||||
reply_mock.attribute.return_value = 200
|
reply_mock.attribute.return_value = 200
|
||||||
reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("ascii"))
|
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 = SendMaterialJob(device_mock)
|
job._onGetRemoteMaterials(reply_mock)
|
||||||
job._onGetRemoteMaterials(reply_mock)
|
|
||||||
|
|
||||||
self.assertEqual(1, device_mock.createFormPart.call_count)
|
self.assertEqual(1, device_mock.createFormPart.call_count)
|
||||||
self.assertEqual(1, device_mock.postFormWithParts.call_count)
|
self.assertEqual(1, device_mock.postFormWithParts.call_count)
|
||||||
|
@ -164,16 +214,21 @@ class TestSendMaterialJob(TestCase):
|
||||||
call.postFormWithParts(target = "materials/", parts = ["_xXx_"], on_finished = job.sendingFinished)],
|
call.postFormWithParts(target = "materials/", parts = ["_xXx_"], on_finished = job.sendingFinished)],
|
||||||
device_mock.method_calls)
|
device_mock.method_calls)
|
||||||
|
|
||||||
@patch("cura.Settings.CuraContainerRegistry")
|
@patch("UM.Application.Application.getInstance")
|
||||||
@patch("UM.Application")
|
def test__onGetRemoteMaterials_withNewMaterial(self, application_mock):
|
||||||
def test__onGetRemoteMaterials_withNewMaterial(self, application_mock, container_registry_mock, reply_mock,
|
reply_mock = MagicMock()
|
||||||
device_mock):
|
device_mock = MagicMock()
|
||||||
application_mock.getContainerRegistry.return_value = container_registry_mock
|
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_"
|
device_mock.createFormPart.return_value = "_xXx_"
|
||||||
|
|
||||||
container_registry_mock.findContainersMetadata.return_value = [self._LOCAL_MATERIAL_WHITE,
|
all_results = self._LOCAL_MATERIAL_WHITE_ALL_RESULT.copy()
|
||||||
self._LOCAL_MATERIAL_BLACK]
|
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.attribute.return_value = 200
|
||||||
reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_BLACK]).encode("ascii"))
|
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"
|
"mimetype": "application/x-ultimaker-material-profile"
|
||||||
},
|
},
|
||||||
"version_upgrade": {
|
"version_upgrade": {
|
||||||
("materials", 1000000): ("materials", 1000004, upgrader.upgradeMaterial),
|
("materials", 1000000): ("materials", 1000006, upgrader.upgradeMaterial),
|
||||||
},
|
},
|
||||||
"sources": {
|
"sources": {
|
||||||
"materials": {
|
"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": {
|
"CuraEngineBackend": {
|
||||||
"package_info": {
|
"package_info": {
|
||||||
"package_id": "CuraEngineBackend",
|
"package_id": "CuraEngineBackend",
|
||||||
|
@ -1585,4 +1602,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -4,6 +4,7 @@
|
||||||
import QtQuick 2.7
|
import QtQuick 2.7
|
||||||
import QtQuick.Controls 2.1
|
import QtQuick.Controls 2.1
|
||||||
import QtGraphicalEffects 1.0 // For the dropshadow
|
import QtGraphicalEffects 1.0 // For the dropshadow
|
||||||
|
|
||||||
import UM 1.1 as UM
|
import UM 1.1 as UM
|
||||||
import Cura 1.0 as Cura
|
import Cura 1.0 as Cura
|
||||||
|
|
||||||
|
@ -30,6 +31,7 @@ Button
|
||||||
property color outlineDisabledColor: outlineColor
|
property color outlineDisabledColor: outlineColor
|
||||||
property alias shadowColor: shadow.color
|
property alias shadowColor: shadow.color
|
||||||
property alias shadowEnabled: shadow.visible
|
property alias shadowEnabled: shadow.visible
|
||||||
|
property alias busy: busyIndicator.visible
|
||||||
|
|
||||||
property alias toolTipContentAlignment: tooltip.contentAlignment
|
property alias toolTipContentAlignment: tooltip.contentAlignment
|
||||||
|
|
||||||
|
@ -55,7 +57,7 @@ Button
|
||||||
width: visible ? height : 0
|
width: visible ? height : 0
|
||||||
sourceSize.width: width
|
sourceSize.width: width
|
||||||
sourceSize.height: height
|
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
|
visible: source != "" && !button.isIconOnRightSide
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
@ -117,4 +119,16 @@ Button
|
||||||
id: tooltip
|
id: tooltip
|
||||||
visible: button.hovered
|
visible: button.hovered
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BusyIndicator
|
||||||
|
{
|
||||||
|
id: busyIndicator
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
width: height
|
||||||
|
height: parent.height
|
||||||
|
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -31,6 +31,13 @@ Column
|
||||||
id: information
|
id: information
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: childrenRect.height
|
height: childrenRect.height
|
||||||
|
|
||||||
|
PrintInformationWidget
|
||||||
|
{
|
||||||
|
id: printInformationPanel
|
||||||
|
visible: !preSlicedData
|
||||||
|
anchors.right: parent.right
|
||||||
|
}
|
||||||
|
|
||||||
Column
|
Column
|
||||||
{
|
{
|
||||||
|
@ -50,15 +57,7 @@ Column
|
||||||
|
|
||||||
text: preSlicedData ? catalog.i18nc("@label", "No time estimation available") : PrintInformation.currentPrintTime.getDisplayString(UM.DurationFormat.Long)
|
text: preSlicedData ? catalog.i18nc("@label", "No time estimation available") : PrintInformation.currentPrintTime.getDisplayString(UM.DurationFormat.Long)
|
||||||
source: UM.Theme.getIcon("clock")
|
source: UM.Theme.getIcon("clock")
|
||||||
font: UM.Theme.getFont("large_bold")
|
font: UM.Theme.getFont("medium_bold")
|
||||||
|
|
||||||
PrintInformationWidget
|
|
||||||
{
|
|
||||||
id: printInformationPanel
|
|
||||||
visible: !preSlicedData
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: parent.contentWidth + UM.Theme.getSize("default_margin").width
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Cura.IconWithText
|
Cura.IconWithText
|
||||||
|
@ -91,43 +90,8 @@ Column
|
||||||
return totalWeights + "g · " + totalLengths.toFixed(2) + "m"
|
return totalWeights + "g · " + totalLengths.toFixed(2) + "m"
|
||||||
}
|
}
|
||||||
source: UM.Theme.getIcon("spool")
|
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
|
Item
|
||||||
|
|
|
@ -110,8 +110,7 @@ Column
|
||||||
|
|
||||||
height: parent.height
|
height: parent.height
|
||||||
|
|
||||||
anchors.right: additionalComponents.left
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: additionalComponents.width != 0 ? UM.Theme.getSize("default_margin").width : 0
|
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
|
||||||
text: catalog.i18nc("@button", "Slice")
|
text: catalog.i18nc("@button", "Slice")
|
||||||
|
@ -128,45 +127,12 @@ Column
|
||||||
height: parent.height
|
height: parent.height
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
|
||||||
anchors.right: additionalComponents.left
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: additionalComponents.width != 0 ? UM.Theme.getSize("default_margin").width : 0
|
|
||||||
text: catalog.i18nc("@button", "Cancel")
|
text: catalog.i18nc("@button", "Cancel")
|
||||||
enabled: sliceButton.enabled
|
enabled: sliceButton.enabled
|
||||||
visible: !sliceButton.visible
|
visible: !sliceButton.visible
|
||||||
onClicked: sliceOrStopSlicing()
|
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"
|
shortcut: "Ctrl+P"
|
||||||
onTriggered:
|
onTriggered:
|
||||||
{
|
{
|
||||||
if (prepareButton.enabled)
|
if (sliceButton.enabled)
|
||||||
{
|
{
|
||||||
sliceOrStopSlicing()
|
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
|
||||||
|
}
|
||||||
|
}
|
|
@ -124,16 +124,16 @@ UM.MainWindow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a placehoder for adding a pattern in the header
|
// This is a placehoder for adding a pattern in the header
|
||||||
Image
|
Image
|
||||||
{
|
{
|
||||||
id: backgroundPattern
|
id: backgroundPattern
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
fillMode: Image.Tile
|
fillMode: Image.Tile
|
||||||
source: UM.Theme.getImage("header_pattern")
|
source: UM.Theme.getImage("header_pattern")
|
||||||
horizontalAlignment: Image.AlignLeft
|
horizontalAlignment: Image.AlignLeft
|
||||||
verticalAlignment: Image.AlignTop
|
verticalAlignment: Image.AlignTop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWindowHeader
|
MainWindowHeader
|
||||||
|
@ -248,11 +248,59 @@ UM.MainWindow
|
||||||
|
|
||||||
Cura.ActionPanelWidget
|
Cura.ActionPanelWidget
|
||||||
{
|
{
|
||||||
|
id: actionPanelWidget
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.rightMargin: UM.Theme.getSize("thick_margin").width
|
anchors.rightMargin: UM.Theme.getSize("thick_margin").width
|
||||||
anchors.bottomMargin: UM.Theme.getSize("thick_margin").height
|
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
|
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
|
Item
|
||||||
{
|
{
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
@ -51,7 +51,11 @@ Item
|
||||||
anchors.left: icon.right
|
anchors.left: icon.right
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
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")
|
color: UM.Theme.getColor("text")
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
renderType: Text.NativeRendering
|
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
|
Rectangle
|
||||||
{
|
{
|
||||||
id: separator
|
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.I18nCatalog { id: catalog; name: "cura"; }
|
||||||
|
|
||||||
UM.ConfirmRemoveDialog
|
UM.ConfirmRemoveDialog
|
||||||
{
|
{
|
||||||
id: confirmDialog;
|
id: confirmDialog
|
||||||
object: base.currentItem && base.currentItem.name ? base.currentItem.name : "";
|
object: base.currentItem && base.currentItem.name ? base.currentItem.name : ""
|
||||||
onYes:
|
onYes:
|
||||||
{
|
{
|
||||||
Cura.MachineManager.removeMachine(base.currentItem.id);
|
Cura.MachineManager.removeMachine(base.currentItem.id)
|
||||||
if(!base.currentItem)
|
if(!base.currentItem)
|
||||||
{
|
{
|
||||||
objectList.currentIndex = activeMachineIndex()
|
objectList.currentIndex = activeMachineIndex()
|
||||||
|
|
|
@ -376,6 +376,7 @@ Item
|
||||||
|
|
||||||
width: true ? (parent.width * 0.4) | 0 : parent.width
|
width: true ? (parent.width * 0.4) | 0 : parent.width
|
||||||
frameVisible: true
|
frameVisible: true
|
||||||
|
clip: true
|
||||||
|
|
||||||
ListView
|
ListView
|
||||||
{
|
{
|
||||||
|
|
|
@ -176,6 +176,17 @@ Item
|
||||||
UM.Preferences.setValue("view/settings_list_height", h);
|
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
|
ToolbarButton 1.0 ToolbarButton.qml
|
||||||
SettingView 1.0 SettingView.qml
|
SettingView 1.0 SettingView.qml
|
||||||
ProfileMenu 1.0 ProfileMenu.qml
|
ProfileMenu 1.0 ProfileMenu.qml
|
||||||
ToolTip 1.0 ToolTip.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"))
|
color: (control.hovered || control._hovered) ? Theme.getColor("checkbox_hover") : (control.enabled ? Theme.getColor("checkbox") : Theme.getColor("checkbox_disabled"))
|
||||||
Behavior on color { ColorAnimation { duration: 50; } }
|
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.width: Theme.getSize("default_lining").width
|
||||||
border.color: (control.hovered || control._hovered) ? Theme.getColor("checkbox_border_hover") : Theme.getColor("checkbox_border")
|
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 : ""
|
text: control.unit ? control.unit : ""
|
||||||
color: Theme.getColor("setting_unit");
|
color: Theme.getColor("setting_unit");
|
||||||
font: Theme.getFont("default");
|
font: Theme.getFont("default");
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,21 @@
|
||||||
"weight": 40,
|
"weight": 40,
|
||||||
"family": "Noto Sans"
|
"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": {
|
"large_bold": {
|
||||||
"size": 1.35,
|
"size": 1.35,
|
||||||
"weight": 63,
|
"weight": 63,
|
||||||
|
@ -19,6 +34,21 @@
|
||||||
"weight": 40,
|
"weight": 40,
|
||||||
"family": "Noto Sans"
|
"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": {
|
"medium_bold": {
|
||||||
"size": 1.16,
|
"size": 1.16,
|
||||||
"weight": 63,
|
"weight": 63,
|
||||||
|
@ -29,21 +59,84 @@
|
||||||
"weight": 40,
|
"weight": 40,
|
||||||
"family": "Noto Sans"
|
"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": {
|
"default_bold": {
|
||||||
"size": 0.95,
|
"size": 0.95,
|
||||||
"weight": 63,
|
"weight": 63,
|
||||||
"family": "Noto Sans"
|
"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": {
|
"default_italic": {
|
||||||
"size": 0.95,
|
"size": 0.95,
|
||||||
"weight": 40,
|
"weight": 40,
|
||||||
"italic": true,
|
"italic": true,
|
||||||
"family": "Noto Sans"
|
"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": {
|
"small": {
|
||||||
"size": 0.7,
|
"size": 0.7,
|
||||||
"weight": 40,
|
"weight": 40,
|
||||||
"family": "Noto Sans"
|
"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