Merge branch 'STAR-322_cloud-connection' of github.com:Ultimaker/Cura into STAR-322_cloud-connection-multipart-upload
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 CuraConstants
|
||||||
|
|
||||||
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,7 +38,7 @@ class Account(QObject):
|
||||||
self._logged_in = False
|
self._logged_in = False
|
||||||
|
|
||||||
self._callback_port = 32118
|
self._callback_port = 32118
|
||||||
self._oauth_root = "https://account-staging.ultimaker.com"
|
self._oauth_root = CuraConstants.CuraCloudAccountAPIRoot
|
||||||
|
|
||||||
self._oauth_settings = OAuth2Settings(
|
self._oauth_settings = OAuth2Settings(
|
||||||
OAUTH_SERVER_URL= self._oauth_root,
|
OAUTH_SERVER_URL= self._oauth_root,
|
||||||
|
|
|
@ -83,7 +83,14 @@ class BuildVolume(SceneNode):
|
||||||
" with printed models."), title = catalog.i18nc("@info:title", "Build Volume"))
|
" with printed models."), title = catalog.i18nc("@info:title", "Build Volume"))
|
||||||
|
|
||||||
self._global_container_stack = None
|
self._global_container_stack = None
|
||||||
|
|
||||||
|
self._stack_change_timer = QTimer()
|
||||||
|
self._stack_change_timer.setInterval(100)
|
||||||
|
self._stack_change_timer.setSingleShot(True)
|
||||||
|
self._stack_change_timer.timeout.connect(self._onStackChangeTimerFinished)
|
||||||
|
|
||||||
self._application.globalContainerStackChanged.connect(self._onStackChanged)
|
self._application.globalContainerStackChanged.connect(self._onStackChanged)
|
||||||
|
|
||||||
self._onStackChanged()
|
self._onStackChanged()
|
||||||
|
|
||||||
self._engine_ready = False
|
self._engine_ready = False
|
||||||
|
@ -105,6 +112,8 @@ class BuildVolume(SceneNode):
|
||||||
self._setting_change_timer.setSingleShot(True)
|
self._setting_change_timer.setSingleShot(True)
|
||||||
self._setting_change_timer.timeout.connect(self._onSettingChangeTimerFinished)
|
self._setting_change_timer.timeout.connect(self._onSettingChangeTimerFinished)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Must be after setting _build_volume_message, apparently that is used in getMachineManager.
|
# Must be after setting _build_volume_message, apparently that is used in getMachineManager.
|
||||||
# activeQualityChanged is always emitted after setActiveVariant, setActiveMaterial and setActiveQuality.
|
# activeQualityChanged is always emitted after setActiveVariant, setActiveMaterial and setActiveQuality.
|
||||||
# Therefore this works.
|
# Therefore this works.
|
||||||
|
@ -526,8 +535,11 @@ class BuildVolume(SceneNode):
|
||||||
if extra_z != self._extra_z_clearance:
|
if extra_z != self._extra_z_clearance:
|
||||||
self._extra_z_clearance = extra_z
|
self._extra_z_clearance = extra_z
|
||||||
|
|
||||||
## Update the build volume visualization
|
|
||||||
def _onStackChanged(self):
|
def _onStackChanged(self):
|
||||||
|
self._stack_change_timer.start()
|
||||||
|
|
||||||
|
## Update the build volume visualization
|
||||||
|
def _onStackChangeTimerFinished(self):
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
self._global_container_stack.propertyChanged.disconnect(self._onSettingPropertyChanged)
|
self._global_container_stack.propertyChanged.disconnect(self._onSettingPropertyChanged)
|
||||||
extruders = ExtruderManager.getInstance().getActiveExtruderStacks()
|
extruders = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||||
|
|
|
@ -115,6 +115,8 @@ from cura.ObjectsModel import ObjectsModel
|
||||||
|
|
||||||
from cura.PrinterOutput.NetworkMJPGImage import NetworkMJPGImage
|
from cura.PrinterOutput.NetworkMJPGImage import NetworkMJPGImage
|
||||||
|
|
||||||
|
from cura import CuraConstants
|
||||||
|
|
||||||
from UM.FlameProfiler import pyqtSlot
|
from UM.FlameProfiler import pyqtSlot
|
||||||
from UM.Decorators import override
|
from UM.Decorators import override
|
||||||
|
|
||||||
|
@ -127,15 +129,6 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
numpy.seterr(all = "ignore")
|
numpy.seterr(all = "ignore")
|
||||||
|
|
||||||
try:
|
|
||||||
from cura.CuraVersion import CuraAppDisplayName, CuraVersion, CuraBuildType, CuraDebugMode, CuraSDKVersion # type: ignore
|
|
||||||
except ImportError:
|
|
||||||
CuraAppDisplayName = "Ultimaker Cura"
|
|
||||||
CuraVersion = "master" # [CodeStyle: Reflecting imported value]
|
|
||||||
CuraBuildType = ""
|
|
||||||
CuraDebugMode = False
|
|
||||||
CuraSDKVersion = "5.0.0"
|
|
||||||
|
|
||||||
|
|
||||||
class CuraApplication(QtApplication):
|
class CuraApplication(QtApplication):
|
||||||
# SettingVersion represents the set of settings available in the machine/extruder definitions.
|
# SettingVersion represents the set of settings available in the machine/extruder definitions.
|
||||||
|
@ -162,11 +155,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 = CuraConstants.CuraAppDisplayName,
|
||||||
version = CuraVersion,
|
version = CuraConstants.CuraVersion,
|
||||||
api_version = CuraSDKVersion,
|
api_version = CuraConstants.CuraSDKVersion,
|
||||||
buildtype = CuraBuildType,
|
buildtype = CuraConstants.CuraBuildType,
|
||||||
is_debug_mode = CuraDebugMode,
|
is_debug_mode = CuraConstants.CuraDebugMode,
|
||||||
tray_icon_name = "cura-icon-32.png",
|
tray_icon_name = "cura-icon-32.png",
|
||||||
**kwargs)
|
**kwargs)
|
||||||
|
|
||||||
|
@ -936,7 +929,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", CuraConstants.CuraSDKVersion)
|
||||||
|
|
||||||
qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type")
|
qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type")
|
||||||
|
|
||||||
|
|
60
cura/CuraConstants.py
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
#
|
||||||
|
# This file contains all constant values in Cura
|
||||||
|
#
|
||||||
|
|
||||||
|
# -------------
|
||||||
|
# Cura Versions
|
||||||
|
# -------------
|
||||||
|
DEFAULT_CURA_DISPLAY_NAME = "Ultimaker Cura"
|
||||||
|
DEFAULT_CURA_VERSION = "master"
|
||||||
|
DEFAULT_CURA_BUILD_TYPE = ""
|
||||||
|
DEFAULT_CURA_DEBUG_MODE = False
|
||||||
|
DEFAULT_CURA_SDK_VERSION = "5.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
|
||||||
|
|
||||||
|
|
||||||
|
# ---------
|
||||||
|
# Cloud API
|
||||||
|
# ---------
|
||||||
|
DEFAULT_CLOUD_API_ROOT = "https://api.ultimaker.com" # type: str
|
||||||
|
DEFAULT_CLOUD_API_VERSION = "1" # type: str
|
||||||
|
DEFAULT_CLOUD_ACCOUNT_API_ROOT = "https://account.ultimaker.com" # type: str
|
||||||
|
|
||||||
|
try:
|
||||||
|
from cura.CuraVersion import CuraCloudAPIRoot # type: ignore
|
||||||
|
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
|
||||||
|
except ImportError:
|
||||||
|
CuraCloudAccountAPIRoot = DEFAULT_CLOUD_ACCOUNT_API_ROOT
|
|
@ -64,21 +64,21 @@ class MachineErrorChecker(QObject):
|
||||||
|
|
||||||
def _onMachineChanged(self) -> None:
|
def _onMachineChanged(self) -> None:
|
||||||
if self._global_stack:
|
if self._global_stack:
|
||||||
self._global_stack.propertyChanged.disconnect(self.startErrorCheck)
|
self._global_stack.propertyChanged.disconnect(self.startErrorCheckPropertyChanged)
|
||||||
self._global_stack.containersChanged.disconnect(self.startErrorCheck)
|
self._global_stack.containersChanged.disconnect(self.startErrorCheck)
|
||||||
|
|
||||||
for extruder in self._global_stack.extruders.values():
|
for extruder in self._global_stack.extruders.values():
|
||||||
extruder.propertyChanged.disconnect(self.startErrorCheck)
|
extruder.propertyChanged.disconnect(self.startErrorCheckPropertyChanged)
|
||||||
extruder.containersChanged.disconnect(self.startErrorCheck)
|
extruder.containersChanged.disconnect(self.startErrorCheck)
|
||||||
|
|
||||||
self._global_stack = self._machine_manager.activeMachine
|
self._global_stack = self._machine_manager.activeMachine
|
||||||
|
|
||||||
if self._global_stack:
|
if self._global_stack:
|
||||||
self._global_stack.propertyChanged.connect(self.startErrorCheck)
|
self._global_stack.propertyChanged.connect(self.startErrorCheckPropertyChanged)
|
||||||
self._global_stack.containersChanged.connect(self.startErrorCheck)
|
self._global_stack.containersChanged.connect(self.startErrorCheck)
|
||||||
|
|
||||||
for extruder in self._global_stack.extruders.values():
|
for extruder in self._global_stack.extruders.values():
|
||||||
extruder.propertyChanged.connect(self.startErrorCheck)
|
extruder.propertyChanged.connect(self.startErrorCheckPropertyChanged)
|
||||||
extruder.containersChanged.connect(self.startErrorCheck)
|
extruder.containersChanged.connect(self.startErrorCheck)
|
||||||
|
|
||||||
hasErrorUpdated = pyqtSignal()
|
hasErrorUpdated = pyqtSignal()
|
||||||
|
@ -93,6 +93,13 @@ class MachineErrorChecker(QObject):
|
||||||
def needToWaitForResult(self) -> bool:
|
def needToWaitForResult(self) -> bool:
|
||||||
return self._need_to_check or self._check_in_progress
|
return self._need_to_check or self._check_in_progress
|
||||||
|
|
||||||
|
# Start the error check for property changed
|
||||||
|
# this is seperate from the startErrorCheck because it ignores a number property types
|
||||||
|
def startErrorCheckPropertyChanged(self, key, property_name):
|
||||||
|
if property_name != "value":
|
||||||
|
return
|
||||||
|
self.startErrorCheck()
|
||||||
|
|
||||||
# Starts the error check timer to schedule a new error check.
|
# Starts the error check timer to schedule a new error check.
|
||||||
def startErrorCheck(self, *args) -> None:
|
def startErrorCheck(self, *args) -> None:
|
||||||
if not self._check_in_progress:
|
if not self._check_in_progress:
|
||||||
|
|
|
@ -1,5 +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 Optional, Dict, Set
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
|
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
|
||||||
from UM.Qt.ListModel import ListModel
|
from UM.Qt.ListModel import ListModel
|
||||||
|
@ -9,6 +10,9 @@ from UM.Qt.ListModel import ListModel
|
||||||
# Those 2 models are used by the material drop down menu to show generic materials and branded materials separately.
|
# Those 2 models are used by the material drop down menu to show generic materials and branded materials separately.
|
||||||
# The extruder position defined here is being used to bound a menu to the correct extruder. This is used in the top
|
# The extruder position defined here is being used to bound a menu to the correct extruder. This is used in the top
|
||||||
# bar menu "Settings" -> "Extruder nr" -> "Material" -> this menu
|
# bar menu "Settings" -> "Extruder nr" -> "Material" -> this menu
|
||||||
|
from cura.Machines.MaterialNode import MaterialNode
|
||||||
|
|
||||||
|
|
||||||
class BaseMaterialsModel(ListModel):
|
class BaseMaterialsModel(ListModel):
|
||||||
|
|
||||||
extruderPositionChanged = pyqtSignal()
|
extruderPositionChanged = pyqtSignal()
|
||||||
|
@ -54,8 +58,8 @@ class BaseMaterialsModel(ListModel):
|
||||||
self._extruder_position = 0
|
self._extruder_position = 0
|
||||||
self._extruder_stack = None
|
self._extruder_stack = None
|
||||||
|
|
||||||
self._available_materials = None
|
self._available_materials = None # type: Optional[Dict[str, MaterialNode]]
|
||||||
self._favorite_ids = None
|
self._favorite_ids = set() # type: Set[str]
|
||||||
|
|
||||||
def _updateExtruderStack(self):
|
def _updateExtruderStack(self):
|
||||||
global_stack = self._machine_manager.activeMachine
|
global_stack = self._machine_manager.activeMachine
|
||||||
|
@ -102,8 +106,10 @@ class BaseMaterialsModel(ListModel):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
extruder_stack = global_stack.extruders[extruder_position]
|
extruder_stack = global_stack.extruders[extruder_position]
|
||||||
|
available_materials = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack, extruder_stack)
|
||||||
self._available_materials = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack, extruder_stack)
|
if available_materials == self._available_materials:
|
||||||
|
return False
|
||||||
|
self._available_materials = available_materials
|
||||||
if self._available_materials is None:
|
if self._available_materials is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
|
@ -4,17 +4,14 @@
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
|
from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
|
||||||
|
|
||||||
class FavoriteMaterialsModel(BaseMaterialsModel):
|
|
||||||
|
|
||||||
|
class FavoriteMaterialsModel(BaseMaterialsModel):
|
||||||
def __init__(self, parent = None):
|
def __init__(self, parent = None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._update()
|
self._update()
|
||||||
|
|
||||||
def _update(self):
|
def _update(self):
|
||||||
|
|
||||||
# Perform standard check and reset if the check fails
|
|
||||||
if not self._canUpdate():
|
if not self._canUpdate():
|
||||||
self.setItems([])
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get updated list of favorites
|
# Get updated list of favorites
|
||||||
|
|
|
@ -11,10 +11,7 @@ class GenericMaterialsModel(BaseMaterialsModel):
|
||||||
self._update()
|
self._update()
|
||||||
|
|
||||||
def _update(self):
|
def _update(self):
|
||||||
|
|
||||||
# Perform standard check and reset if the check fails
|
|
||||||
if not self._canUpdate():
|
if not self._canUpdate():
|
||||||
self.setItems([])
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get updated list of favorites
|
# Get updated list of favorites
|
||||||
|
|
|
@ -28,12 +28,8 @@ class MaterialBrandsModel(BaseMaterialsModel):
|
||||||
self._update()
|
self._update()
|
||||||
|
|
||||||
def _update(self):
|
def _update(self):
|
||||||
|
|
||||||
# Perform standard check and reset if the check fails
|
|
||||||
if not self._canUpdate():
|
if not self._canUpdate():
|
||||||
self.setItems([])
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get updated list of favorites
|
# Get updated list of favorites
|
||||||
self._favorite_ids = self._material_manager.getFavorites()
|
self._favorite_ids = self._material_manager.getFavorites()
|
||||||
|
|
||||||
|
|
|
@ -81,9 +81,14 @@ class AuthorizationHelpers:
|
||||||
# \param access_token: The encoded JWT token.
|
# \param access_token: The encoded JWT token.
|
||||||
# \return: Dict containing some profile data.
|
# \return: Dict containing some profile data.
|
||||||
def parseJWT(self, access_token: str) -> Optional["UserProfile"]:
|
def parseJWT(self, access_token: str) -> Optional["UserProfile"]:
|
||||||
token_request = requests.get("{}/check-token".format(self._settings.OAUTH_SERVER_URL), headers = {
|
try:
|
||||||
"Authorization": "Bearer {}".format(access_token)
|
token_request = requests.get("{}/check-token".format(self._settings.OAUTH_SERVER_URL), headers = {
|
||||||
})
|
"Authorization": "Bearer {}".format(access_token)
|
||||||
|
})
|
||||||
|
except ConnectionError:
|
||||||
|
# Connection was suddenly dropped. Nothing we can do about that.
|
||||||
|
Logger.logException("e", "Something failed while attempting to parse the JWT token")
|
||||||
|
return None
|
||||||
if token_request.status_code not in (200, 201):
|
if token_request.status_code not in (200, 201):
|
||||||
Logger.log("w", "Could not retrieve token data from auth server: %s", token_request.text)
|
Logger.log("w", "Could not retrieve token data from auth server: %s", token_request.text)
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -44,7 +44,7 @@ class ConfigurationModel(QObject):
|
||||||
|
|
||||||
@pyqtProperty(str, fset = setBuildplateConfiguration, notify = configurationChanged)
|
@pyqtProperty(str, fset = setBuildplateConfiguration, notify = configurationChanged)
|
||||||
def buildplateConfiguration(self) -> str:
|
def buildplateConfiguration(self) -> str:
|
||||||
return self._buildplate_configuration.capitalize()
|
return self._buildplate_configuration
|
||||||
|
|
||||||
## This method is intended to indicate whether the configuration is valid or not.
|
## This method is intended to indicate whether the configuration is valid or not.
|
||||||
# The method checks if the mandatory fields are or not set
|
# The method checks if the mandatory fields are or not set
|
||||||
|
|
|
@ -83,8 +83,9 @@ class ExtruderManager(QObject):
|
||||||
# \param index The index of the new active extruder.
|
# \param index The index of the new active extruder.
|
||||||
@pyqtSlot(int)
|
@pyqtSlot(int)
|
||||||
def setActiveExtruderIndex(self, index: int) -> None:
|
def setActiveExtruderIndex(self, index: int) -> None:
|
||||||
self._active_extruder_index = index
|
if self._active_extruder_index != index:
|
||||||
self.activeExtruderChanged.emit()
|
self._active_extruder_index = index
|
||||||
|
self.activeExtruderChanged.emit()
|
||||||
|
|
||||||
@pyqtProperty(int, notify = activeExtruderChanged)
|
@pyqtProperty(int, notify = activeExtruderChanged)
|
||||||
def activeExtruderIndex(self) -> int:
|
def activeExtruderIndex(self) -> int:
|
||||||
|
@ -344,6 +345,7 @@ class ExtruderManager(QObject):
|
||||||
if extruders_changed:
|
if extruders_changed:
|
||||||
self.extrudersChanged.emit(global_stack_id)
|
self.extrudersChanged.emit(global_stack_id)
|
||||||
self.setActiveExtruderIndex(0)
|
self.setActiveExtruderIndex(0)
|
||||||
|
self.activeExtruderChanged.emit()
|
||||||
|
|
||||||
# After 3.4, all single-extrusion machines have their own extruder definition files instead of reusing
|
# After 3.4, all single-extrusion machines have their own extruder definition files instead of reusing
|
||||||
# "fdmextruder". We need to check a machine here so its extruder definition is correct according to this.
|
# "fdmextruder". We need to check a machine here so its extruder definition is correct according to this.
|
||||||
|
|
|
@ -224,6 +224,6 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
||||||
"definition": ""
|
"definition": ""
|
||||||
}
|
}
|
||||||
items.append(item)
|
items.append(item)
|
||||||
|
if self._items != items:
|
||||||
self.setItems(items)
|
self.setItems(items)
|
||||||
self.modelChanged.emit()
|
self.modelChanged.emit()
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import threading
|
import threading
|
||||||
from typing import Any, Dict, Optional, Set, TYPE_CHECKING
|
from typing import Any, Dict, Optional, Set, TYPE_CHECKING, List
|
||||||
from PyQt5.QtCore import pyqtProperty, pyqtSlot
|
from PyQt5.QtCore import pyqtProperty, pyqtSlot, pyqtSignal
|
||||||
|
|
||||||
from UM.Decorators import override
|
from UM.Decorators import override
|
||||||
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
|
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
|
||||||
|
@ -42,13 +42,23 @@ class GlobalStack(CuraContainerStack):
|
||||||
# Per thread we have our own resolving_settings, or strange things sometimes occur.
|
# Per thread we have our own resolving_settings, or strange things sometimes occur.
|
||||||
self._resolving_settings = defaultdict(set) #type: Dict[str, Set[str]] # keys are thread names
|
self._resolving_settings = defaultdict(set) #type: Dict[str, Set[str]] # keys are thread names
|
||||||
|
|
||||||
|
extrudersChanged = pyqtSignal()
|
||||||
|
|
||||||
## Get the list of extruders of this stack.
|
## Get the list of extruders of this stack.
|
||||||
#
|
#
|
||||||
# \return The extruders registered with this stack.
|
# \return The extruders registered with this stack.
|
||||||
@pyqtProperty("QVariantMap")
|
@pyqtProperty("QVariantMap", notify = extrudersChanged)
|
||||||
def extruders(self) -> Dict[str, "ExtruderStack"]:
|
def extruders(self) -> Dict[str, "ExtruderStack"]:
|
||||||
return self._extruders
|
return self._extruders
|
||||||
|
|
||||||
|
@pyqtProperty("QVariantList", notify = extrudersChanged)
|
||||||
|
def extruderList(self) -> List["ExtruderStack"]:
|
||||||
|
result_tuple_list = sorted(list(self.extruders.items()), key=lambda x: int(x[0]))
|
||||||
|
result_list = [item[1] for item in result_tuple_list]
|
||||||
|
|
||||||
|
machine_extruder_count = self.getProperty("machine_extruder_count", "value")
|
||||||
|
return result_list[:machine_extruder_count]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def getLoadingPriority(cls) -> int:
|
def getLoadingPriority(cls) -> int:
|
||||||
return 2
|
return 2
|
||||||
|
|
|
@ -24,10 +24,6 @@ class CuraStage(Stage):
|
||||||
def mainComponent(self) -> QUrl:
|
def mainComponent(self) -> QUrl:
|
||||||
return self.getDisplayComponent("main")
|
return self.getDisplayComponent("main")
|
||||||
|
|
||||||
@pyqtProperty(QUrl, constant = True)
|
|
||||||
def sidebarComponent(self) -> QUrl:
|
|
||||||
return self.getDisplayComponent("sidebar")
|
|
||||||
|
|
||||||
@pyqtProperty(QUrl, constant = True)
|
@pyqtProperty(QUrl, constant = True)
|
||||||
def stageMenuComponent(self) -> QUrl:
|
def stageMenuComponent(self) -> QUrl:
|
||||||
return self.getDisplayComponent("menu")
|
return self.getDisplayComponent("menu")
|
14
plugins/CuraDrive/__init__.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
# Copyright (c) 2017 Ultimaker B.V.
|
||||||
|
import os
|
||||||
|
|
||||||
|
is_testing = os.getenv('ENV_NAME', "development") == "testing"
|
||||||
|
|
||||||
|
# Only load the whole plugin when not running tests as __init__.py is automatically loaded by PyTest
|
||||||
|
if not is_testing:
|
||||||
|
from .src.DrivePluginExtension import DrivePluginExtension
|
||||||
|
|
||||||
|
def getMetaData():
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def register(app):
|
||||||
|
return {"extension": DrivePluginExtension(app)}
|
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": 5,
|
||||||
|
"i18n-catalog": "cura"
|
||||||
|
}
|
185
plugins/CuraDrive/src/DriveApiService.py
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
# Copyright (c) 2017 Ultimaker B.V.
|
||||||
|
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
|
||||||
|
|
||||||
|
from .UploadBackupJob import UploadBackupJob
|
||||||
|
from .Settings import Settings
|
||||||
|
|
||||||
|
|
||||||
|
class DriveApiService:
|
||||||
|
"""
|
||||||
|
The DriveApiService is responsible for interacting with the CuraDrive API and Cura's backup handling.
|
||||||
|
"""
|
||||||
|
|
||||||
|
GET_BACKUPS_URL = "{}/backups".format(Settings.DRIVE_API_URL)
|
||||||
|
PUT_BACKUP_URL = "{}/backups".format(Settings.DRIVE_API_URL)
|
||||||
|
DELETE_BACKUP_URL = "{}/backups".format(Settings.DRIVE_API_URL)
|
||||||
|
|
||||||
|
# Emit signal when restoring backup started or finished.
|
||||||
|
onRestoringStateChanged = Signal()
|
||||||
|
|
||||||
|
# Emit signal when creating backup started or finished.
|
||||||
|
onCreatingStateChanged = Signal()
|
||||||
|
|
||||||
|
def __init__(self, cura_api) -> None:
|
||||||
|
"""Create a new instance of the Drive API service and set the cura_api object."""
|
||||||
|
self._cura_api = cura_api
|
||||||
|
|
||||||
|
def getBackups(self) -> List[Dict[str, Any]]:
|
||||||
|
"""Get all backups from the API."""
|
||||||
|
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.GET_BACKUPS_URL, headers={
|
||||||
|
"Authorization": "Bearer {}".format(access_token)
|
||||||
|
})
|
||||||
|
if backup_list_request.status_code > 299:
|
||||||
|
Logger.log("w", "Could not get backups list from remote: %s", backup_list_request.text)
|
||||||
|
Message(Settings.translatable_messages["get_backups_error"], title = Settings.MESSAGE_TITLE,
|
||||||
|
lifetime = 10).show()
|
||||||
|
return []
|
||||||
|
return backup_list_request.json()["data"]
|
||||||
|
|
||||||
|
def createBackup(self) -> None:
|
||||||
|
"""Create a backup and upload it to CuraDrive cloud storage."""
|
||||||
|
self.onCreatingStateChanged.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.onCreatingStateChanged.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.onCreatingStateChanged.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:
|
||||||
|
"""
|
||||||
|
Callback handler for the upload job.
|
||||||
|
:param job: The executed job.
|
||||||
|
"""
|
||||||
|
if job.backup_upload_error_message != "":
|
||||||
|
# If the job contains an error message we pass it along so the UI can display it.
|
||||||
|
self.onCreatingStateChanged.emit(is_creating=False, error_message=job.backup_upload_error_message)
|
||||||
|
else:
|
||||||
|
self.onCreatingStateChanged.emit(is_creating=False)
|
||||||
|
|
||||||
|
def restoreBackup(self, backup: Dict[str, Any]) -> None:
|
||||||
|
"""
|
||||||
|
Restore a previously exported backup from cloud storage.
|
||||||
|
:param backup: A dict containing an entry from the API list response.
|
||||||
|
"""
|
||||||
|
self.onRestoringStateChanged.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 != 200:
|
||||||
|
# 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("data"))
|
||||||
|
self.onRestoringStateChanged.emit(is_restoring=False)
|
||||||
|
|
||||||
|
def _emitRestoreError(self, error_message: str = Settings.translatable_messages["backup_restore_error_message"]):
|
||||||
|
"""Helper method for emitting a signal when restoring failed."""
|
||||||
|
self.onRestoringStateChanged.emit(
|
||||||
|
is_restoring=False,
|
||||||
|
error_message=error_message
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _verifyMd5Hash(file_path: str, known_hash: str) -> bool:
|
||||||
|
"""
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
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:
|
||||||
|
"""
|
||||||
|
Delete a backup from the server by ID.
|
||||||
|
:param backup_id: The ID of the backup to delete.
|
||||||
|
:return: Success 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.DELETE_BACKUP_URL, backup_id), headers = {
|
||||||
|
"Authorization": "Bearer {}".format(access_token)
|
||||||
|
})
|
||||||
|
if delete_backup.status_code > 299:
|
||||||
|
Logger.log("w", "Could not delete backup: %s", delete_backup.text)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _requestBackupUpload(self, backup_metadata: Dict[str, Any], backup_size: int) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
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.PUT_BACKUP_URL, json={
|
||||||
|
"data": {
|
||||||
|
"backup_size": backup_size,
|
||||||
|
"metadata": backup_metadata
|
||||||
|
}
|
||||||
|
}, headers={
|
||||||
|
"Authorization": "Bearer {}".format(access_token)
|
||||||
|
})
|
||||||
|
|
||||||
|
if backup_upload_request.status_code > 299:
|
||||||
|
Logger.log("w", "Could not request backup upload: %s", backup_upload_request.text)
|
||||||
|
return None
|
||||||
|
|
||||||
|
return backup_upload_request.json()["data"]["upload_url"]
|
201
plugins/CuraDrive/src/DrivePluginExtension.py
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
# Copyright (c) 2017 Ultimaker B.V.
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
|
||||||
|
|
||||||
|
from UM.Extension import Extension
|
||||||
|
from UM.Message import Message
|
||||||
|
|
||||||
|
from .Settings import Settings
|
||||||
|
from .DriveApiService import DriveApiService
|
||||||
|
from .models.BackupListModel import BackupListModel
|
||||||
|
|
||||||
|
|
||||||
|
class DrivePluginExtension(QObject, Extension):
|
||||||
|
"""
|
||||||
|
The DivePluginExtension provides functionality to backup and restore your Cura configuration to Ultimaker's cloud.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 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, application):
|
||||||
|
super(DrivePluginExtension, self).__init__()
|
||||||
|
|
||||||
|
# Re-usable instance of application.
|
||||||
|
self._application = application
|
||||||
|
|
||||||
|
# Local data caching for the UI.
|
||||||
|
self._drive_window = None # type: Optional[QObject]
|
||||||
|
self._backups_list_model = BackupListModel()
|
||||||
|
self._is_restoring_backup = False
|
||||||
|
self._is_creating_backup = False
|
||||||
|
|
||||||
|
# Initialize services.
|
||||||
|
self._preferences = self._application.getPreferences()
|
||||||
|
self._cura_api = self._application.getCuraAPI()
|
||||||
|
self._drive_api_service = DriveApiService(self._cura_api)
|
||||||
|
|
||||||
|
# Attach signals.
|
||||||
|
self._cura_api.account.loginStateChanged.connect(self._onLoginStateChanged)
|
||||||
|
self._drive_api_service.onRestoringStateChanged.connect(self._onRestoringStateChanged)
|
||||||
|
self._drive_api_service.onCreatingStateChanged.connect(self._onCreatingStateChanged)
|
||||||
|
|
||||||
|
# Register preferences.
|
||||||
|
self._preferences.addPreference(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY, False)
|
||||||
|
self._preferences.addPreference(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY, datetime.now()
|
||||||
|
.strftime(self.DATE_FORMAT))
|
||||||
|
|
||||||
|
# Register menu items.
|
||||||
|
self._updateMenuItems()
|
||||||
|
|
||||||
|
# Make auto-backup on boot if required.
|
||||||
|
self._application.engineCreatedSignal.connect(self._autoBackup)
|
||||||
|
|
||||||
|
def showDriveWindow(self) -> None:
|
||||||
|
"""Show the Drive UI popup window."""
|
||||||
|
if not self._drive_window:
|
||||||
|
self._drive_window = self.createDriveWindow()
|
||||||
|
self.refreshBackups()
|
||||||
|
if self._drive_window:
|
||||||
|
self._drive_window.show()
|
||||||
|
|
||||||
|
def createDriveWindow(self) -> Optional["QObject"]:
|
||||||
|
"""
|
||||||
|
Create an instance of the Drive UI popup window.
|
||||||
|
:return: The popup window object.
|
||||||
|
"""
|
||||||
|
path = os.path.join(os.path.dirname(__file__), "qml", "main.qml")
|
||||||
|
return self._application.createQmlComponent(path, {"CuraDrive": self})
|
||||||
|
|
||||||
|
def _updateMenuItems(self) -> None:
|
||||||
|
"""Update the menu items."""
|
||||||
|
self.addMenuItem(Settings.translatable_messages["extension_menu_entry"], self.showDriveWindow)
|
||||||
|
|
||||||
|
def _autoBackup(self) -> None:
|
||||||
|
"""Automatically make a backup on boot if enabled."""
|
||||||
|
if self._preferences.getValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY) and self._lastBackupTooLongAgo():
|
||||||
|
self.createBackup()
|
||||||
|
|
||||||
|
def _lastBackupTooLongAgo(self) -> bool:
|
||||||
|
"""Check if the last backup was longer than 1 day ago."""
|
||||||
|
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":
|
||||||
|
"""Get the last backup date as datetime object."""
|
||||||
|
last_backup_date = self._preferences.getValue(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY)
|
||||||
|
return datetime.strptime(last_backup_date, self.DATE_FORMAT)
|
||||||
|
|
||||||
|
def _storeBackupDate(self) -> None:
|
||||||
|
"""Store the current date as last backup date."""
|
||||||
|
backup_date = datetime.now().strftime(self.DATE_FORMAT)
|
||||||
|
self._preferences.setValue(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY, backup_date)
|
||||||
|
|
||||||
|
def _onLoginStateChanged(self, logged_in: bool = False) -> None:
|
||||||
|
"""Callback handler for changes in the login state."""
|
||||||
|
if logged_in:
|
||||||
|
self.refreshBackups()
|
||||||
|
|
||||||
|
def _onRestoringStateChanged(self, is_restoring: bool = False, error_message: str = None) -> None:
|
||||||
|
"""Callback handler for changes in the restoring state."""
|
||||||
|
self._is_restoring_backup = is_restoring
|
||||||
|
self.restoringStateChanged.emit()
|
||||||
|
if error_message:
|
||||||
|
Message(error_message, title = Settings.MESSAGE_TITLE, lifetime = 5).show()
|
||||||
|
|
||||||
|
def _onCreatingStateChanged(self, is_creating: bool = False, error_message: str = None) -> None:
|
||||||
|
"""Callback handler for changes in the creation state."""
|
||||||
|
self._is_creating_backup = is_creating
|
||||||
|
self.creatingStateChanged.emit()
|
||||||
|
if error_message:
|
||||||
|
Message(error_message, title = Settings.MESSAGE_TITLE, lifetime = 5).show()
|
||||||
|
else:
|
||||||
|
self._storeBackupDate()
|
||||||
|
if not is_creating:
|
||||||
|
# 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:
|
||||||
|
"""Enable or disable the auto-backup feature."""
|
||||||
|
self._preferences.setValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY, enabled)
|
||||||
|
self.preferencesChanged.emit()
|
||||||
|
|
||||||
|
@pyqtProperty(bool, notify = preferencesChanged)
|
||||||
|
def autoBackupEnabled(self) -> bool:
|
||||||
|
"""Check if auto-backup is enabled or not."""
|
||||||
|
return bool(self._preferences.getValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY))
|
||||||
|
|
||||||
|
@pyqtProperty(QObject, notify = backupsChanged)
|
||||||
|
def backups(self) -> BackupListModel:
|
||||||
|
"""
|
||||||
|
Get a list of the backups.
|
||||||
|
:return: The backups as Qt List Model.
|
||||||
|
"""
|
||||||
|
return self._backups_list_model
|
||||||
|
|
||||||
|
@pyqtSlot(name = "refreshBackups")
|
||||||
|
def refreshBackups(self) -> None:
|
||||||
|
"""
|
||||||
|
Forcefully refresh the backups list.
|
||||||
|
"""
|
||||||
|
self._backups_list_model.loadBackups(self._drive_api_service.getBackups())
|
||||||
|
self.backupsChanged.emit()
|
||||||
|
|
||||||
|
@pyqtProperty(bool, notify = restoringStateChanged)
|
||||||
|
def isRestoringBackup(self) -> bool:
|
||||||
|
"""
|
||||||
|
Get the current restoring state.
|
||||||
|
:return: Boolean if we are restoring or not.
|
||||||
|
"""
|
||||||
|
return self._is_restoring_backup
|
||||||
|
|
||||||
|
@pyqtProperty(bool, notify = creatingStateChanged)
|
||||||
|
def isCreatingBackup(self) -> bool:
|
||||||
|
"""
|
||||||
|
Get the current creating state.
|
||||||
|
:return: Boolean if we are creating or not.
|
||||||
|
"""
|
||||||
|
return self._is_creating_backup
|
||||||
|
|
||||||
|
@pyqtSlot(str, name = "restoreBackup")
|
||||||
|
def restoreBackup(self, backup_id: str) -> None:
|
||||||
|
"""
|
||||||
|
Download and restore a backup by ID.
|
||||||
|
:param backup_id: The ID of the backup.
|
||||||
|
"""
|
||||||
|
index = self._backups_list_model.find("backup_id", backup_id)
|
||||||
|
backup = self._backups_list_model.getItem(index)
|
||||||
|
self._drive_api_service.restoreBackup(backup)
|
||||||
|
|
||||||
|
@pyqtSlot(name = "createBackup")
|
||||||
|
def createBackup(self) -> None:
|
||||||
|
"""
|
||||||
|
Create a new backup.
|
||||||
|
"""
|
||||||
|
self._drive_api_service.createBackup()
|
||||||
|
|
||||||
|
@pyqtSlot(str, name = "deleteBackup")
|
||||||
|
def deleteBackup(self, backup_id: str) -> None:
|
||||||
|
"""
|
||||||
|
Delete a backup by ID.
|
||||||
|
:param backup_id: The ID of the backup.
|
||||||
|
"""
|
||||||
|
self._drive_api_service.deleteBackup(backup_id)
|
||||||
|
self.refreshBackups()
|
37
plugins/CuraDrive/src/Settings.py
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
from UM import i18nCatalog
|
||||||
|
|
||||||
|
from cura import CuraConstants
|
||||||
|
|
||||||
|
|
||||||
|
class Settings:
|
||||||
|
"""
|
||||||
|
Keeps the application settings.
|
||||||
|
"""
|
||||||
|
DRIVE_API_VERSION = 1
|
||||||
|
DRIVE_API_URL = "{}/cura-drive/v{}".format(CuraConstants.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"
|
||||||
|
|
||||||
|
I18N_CATALOG_ID = "cura"
|
||||||
|
I18N_CATALOG = i18nCatalog(I18N_CATALOG_ID)
|
||||||
|
|
||||||
|
MESSAGE_TITLE = I18N_CATALOG.i18nc("@info:title", "Backups"),
|
||||||
|
|
||||||
|
# Translatable messages for the entire plugin.
|
||||||
|
translatable_messages = {
|
||||||
|
|
||||||
|
# Menu items.
|
||||||
|
"extension_menu_entry": I18N_CATALOG.i18nc("@item:inmenu", "Manage backups"),
|
||||||
|
|
||||||
|
# Notification messages.
|
||||||
|
"backup_failed": I18N_CATALOG.i18nc("@info:backup_status", "There was an error while creating your backup."),
|
||||||
|
"uploading_backup": I18N_CATALOG.i18nc("@info:backup_status", "Uploading your backup..."),
|
||||||
|
"uploading_backup_success": I18N_CATALOG.i18nc("@info:backup_status", "Your backup has finished uploading."),
|
||||||
|
"uploading_backup_error": I18N_CATALOG.i18nc("@info:backup_status",
|
||||||
|
"There was an error while uploading your backup."),
|
||||||
|
"get_backups_error": I18N_CATALOG.i18nc("@info:backup_status", "There was an error listing your backups."),
|
||||||
|
"backup_restore_error_message": I18N_CATALOG.i18nc("@info:backup_status",
|
||||||
|
"There was an error trying to restore your backup.")
|
||||||
|
}
|
39
plugins/CuraDrive/src/UploadBackupJob.py
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from UM.Job import Job
|
||||||
|
from UM.Logger import Logger
|
||||||
|
from UM.Message import Message
|
||||||
|
|
||||||
|
from .Settings import Settings
|
||||||
|
|
||||||
|
|
||||||
|
class UploadBackupJob(Job):
|
||||||
|
"""
|
||||||
|
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:
|
||||||
|
Message(Settings.translatable_messages["uploading_backup"], title = Settings.MESSAGE_TITLE,
|
||||||
|
lifetime = 10).show()
|
||||||
|
|
||||||
|
backup_upload = requests.put(self._signed_upload_url, data = self._backup_zip)
|
||||||
|
if backup_upload.status_code not in (200, 201):
|
||||||
|
self.backup_upload_error_message = backup_upload.text
|
||||||
|
Logger.log("w", "Could not upload backup file: %s", backup_upload.text)
|
||||||
|
Message(Settings.translatable_messages["uploading_backup_error"], title = Settings.MESSAGE_TITLE,
|
||||||
|
lifetime = 10).show()
|
||||||
|
else:
|
||||||
|
self._upload_success = True
|
||||||
|
Message(Settings.translatable_messages["uploading_backup_success"], title = Settings.MESSAGE_TITLE,
|
||||||
|
lifetime = 10).show()
|
||||||
|
|
||||||
|
self.finished.emit(self)
|
0
plugins/CuraDrive/src/__init__.py
Normal file
38
plugins/CuraDrive/src/models/BackupListModel.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
from typing import Any, List, Dict
|
||||||
|
|
||||||
|
from UM.Qt.ListModel import ListModel
|
||||||
|
|
||||||
|
from PyQt5.QtCore import Qt
|
||||||
|
|
||||||
|
|
||||||
|
class BackupListModel(ListModel):
|
||||||
|
"""
|
||||||
|
The BackupListModel transforms the backups data that came from the server so it can be served to the Qt UI.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, parent = None) -> None:
|
||||||
|
super().__init__(parent)
|
||||||
|
self.addRoleName(Qt.UserRole + 1, "backup_id")
|
||||||
|
self.addRoleName(Qt.UserRole + 2, "download_url")
|
||||||
|
self.addRoleName(Qt.UserRole + 3, "generated_time")
|
||||||
|
self.addRoleName(Qt.UserRole + 4, "md5_hash")
|
||||||
|
self.addRoleName(Qt.UserRole + 5, "data")
|
||||||
|
|
||||||
|
def loadBackups(self, data: List[Dict[str, Any]]) -> None:
|
||||||
|
"""
|
||||||
|
Populate the model with server data.
|
||||||
|
:param data:
|
||||||
|
"""
|
||||||
|
items = []
|
||||||
|
for backup in data:
|
||||||
|
# We do this loop because we only want to append these specific fields.
|
||||||
|
# Without this, ListModel will break.
|
||||||
|
items.append({
|
||||||
|
"backup_id": backup["backup_id"],
|
||||||
|
"download_url": backup["download_url"],
|
||||||
|
"generated_time": backup["generated_time"],
|
||||||
|
"md5_hash": backup["md5_hash"],
|
||||||
|
"data": backup["metadata"]
|
||||||
|
})
|
||||||
|
self.setItems(items)
|
0
plugins/CuraDrive/src/models/__init__.py
Normal file
67
plugins/CuraDrive/src/qml/components/ActionButton.qml
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
import QtQuick 2.7
|
||||||
|
import QtQuick.Controls 2.1
|
||||||
|
import QtQuick.Layouts 1.3
|
||||||
|
|
||||||
|
import UM 1.1 as UM
|
||||||
|
|
||||||
|
Button
|
||||||
|
{
|
||||||
|
id: button
|
||||||
|
property alias cursorShape: mouseArea.cursorShape
|
||||||
|
property var iconSource: ""
|
||||||
|
property var busy: false
|
||||||
|
property var color: UM.Theme.getColor("primary")
|
||||||
|
property var hoverColor: UM.Theme.getColor("primary_hover")
|
||||||
|
property var disabledColor: color
|
||||||
|
property var textColor: UM.Theme.getColor("button_text")
|
||||||
|
property var textHoverColor: UM.Theme.getColor("button_text_hover")
|
||||||
|
property var textDisabledColor: textColor
|
||||||
|
property var textFont: UM.Theme.getFont("action_button")
|
||||||
|
|
||||||
|
contentItem: RowLayout
|
||||||
|
{
|
||||||
|
Icon
|
||||||
|
{
|
||||||
|
id: buttonIcon
|
||||||
|
iconSource: button.iconSource
|
||||||
|
width: 16 * screenScaleFactor
|
||||||
|
color: button.hovered ? button.textHoverColor : button.textColor
|
||||||
|
visible: button.iconSource != "" && !loader.visible
|
||||||
|
}
|
||||||
|
|
||||||
|
Icon
|
||||||
|
{
|
||||||
|
id: loader
|
||||||
|
iconSource: "../images/loading.gif"
|
||||||
|
width: 16 * screenScaleFactor
|
||||||
|
color: button.hovered ? button.textHoverColor : button.textColor
|
||||||
|
visible: button.busy
|
||||||
|
animated: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
id: buttonText
|
||||||
|
text: button.text
|
||||||
|
color: button.enabled ? (button.hovered ? button.textHoverColor : button.textColor): button.textDisabledColor
|
||||||
|
font: button.textFont
|
||||||
|
visible: button.text != ""
|
||||||
|
renderType: Text.NativeRendering
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle
|
||||||
|
{
|
||||||
|
color: button.enabled ? (button.hovered ? button.hoverColor : button.color) : button.disabledColor
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea
|
||||||
|
{
|
||||||
|
id: mouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
onPressed: mouse.accepted = false
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: button.enabled ? (hovered ? Qt.PointingHandCursor : Qt.ArrowCursor) : Qt.ForbiddenCursor
|
||||||
|
}
|
||||||
|
}
|
49
plugins/CuraDrive/src/qml/components/ActionCheckBox.qml
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
import QtQuick 2.7
|
||||||
|
import QtQuick.Controls 2.1
|
||||||
|
import QtQuick.Layouts 1.3
|
||||||
|
|
||||||
|
import UM 1.3 as UM
|
||||||
|
|
||||||
|
CheckBox
|
||||||
|
{
|
||||||
|
id: checkbox
|
||||||
|
hoverEnabled: true
|
||||||
|
|
||||||
|
property var label: ""
|
||||||
|
|
||||||
|
indicator: Rectangle {
|
||||||
|
implicitWidth: 30 * screenScaleFactor
|
||||||
|
implicitHeight: 30 * screenScaleFactor
|
||||||
|
x: 0
|
||||||
|
y: Math.round(parent.height / 2 - height / 2)
|
||||||
|
color: UM.Theme.getColor("sidebar")
|
||||||
|
border.color: UM.Theme.getColor("text")
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 14 * screenScaleFactor
|
||||||
|
height: 14 * screenScaleFactor
|
||||||
|
x: 8 * screenScaleFactor
|
||||||
|
y: 8 * screenScaleFactor
|
||||||
|
color: UM.Theme.getColor("primary")
|
||||||
|
visible: checkbox.checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: Label {
|
||||||
|
anchors
|
||||||
|
{
|
||||||
|
left: checkbox.indicator.right
|
||||||
|
leftMargin: 5 * screenScaleFactor
|
||||||
|
}
|
||||||
|
text: catalog.i18nc("@checkbox:description", "Auto Backup")
|
||||||
|
color: UM.Theme.getColor("text")
|
||||||
|
renderType: Text.NativeRendering
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionToolTip
|
||||||
|
{
|
||||||
|
text: checkbox.label
|
||||||
|
}
|
||||||
|
}
|
29
plugins/CuraDrive/src/qml/components/ActionToolTip.qml
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
import QtQuick 2.7
|
||||||
|
import QtQuick.Controls 2.1
|
||||||
|
import QtQuick.Layouts 1.3
|
||||||
|
|
||||||
|
import UM 1.1 as UM
|
||||||
|
|
||||||
|
ToolTip
|
||||||
|
{
|
||||||
|
id: tooltip
|
||||||
|
visible: parent.hovered
|
||||||
|
opacity: 0.9
|
||||||
|
delay: 500
|
||||||
|
|
||||||
|
background: Rectangle
|
||||||
|
{
|
||||||
|
color: UM.Theme.getColor("sidebar")
|
||||||
|
border.color: UM.Theme.getColor("primary")
|
||||||
|
border.width: 1 * screenScaleFactor
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: Label
|
||||||
|
{
|
||||||
|
text: tooltip.text
|
||||||
|
color: UM.Theme.getColor("text")
|
||||||
|
font: UM.Theme.getFont("very_small")
|
||||||
|
renderType: Text.NativeRendering
|
||||||
|
}
|
||||||
|
}
|
31
plugins/CuraDrive/src/qml/components/BackupList.qml
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
import QtQuick 2.7
|
||||||
|
import QtQuick.Controls 2.1
|
||||||
|
import QtQuick.Layouts 1.3
|
||||||
|
|
||||||
|
import UM 1.1 as UM
|
||||||
|
|
||||||
|
ListView
|
||||||
|
{
|
||||||
|
id: backupList
|
||||||
|
width: parent.width
|
||||||
|
clip: true
|
||||||
|
delegate: Item
|
||||||
|
{
|
||||||
|
width: parent.width
|
||||||
|
height: childrenRect.height
|
||||||
|
|
||||||
|
BackupListItem
|
||||||
|
{
|
||||||
|
id: backupListItem
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
|
||||||
|
Divider
|
||||||
|
{
|
||||||
|
width: parent.width
|
||||||
|
anchors.top: backupListItem.bottom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ScrollBar.vertical: RightSideScrollBar {}
|
||||||
|
}
|
42
plugins/CuraDrive/src/qml/components/BackupListFooter.qml
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
import QtQuick 2.7
|
||||||
|
import QtQuick.Controls 2.1
|
||||||
|
import QtQuick.Layouts 1.3
|
||||||
|
|
||||||
|
import UM 1.3 as UM
|
||||||
|
|
||||||
|
import "../components"
|
||||||
|
|
||||||
|
RowLayout
|
||||||
|
{
|
||||||
|
id: backupListFooter
|
||||||
|
width: parent.width
|
||||||
|
property bool showInfoButton: false
|
||||||
|
|
||||||
|
ActionButton
|
||||||
|
{
|
||||||
|
id: infoButton
|
||||||
|
text: catalog.i18nc("@button", "Want more?")
|
||||||
|
iconSource: "../images/info.svg"
|
||||||
|
onClicked: Qt.openUrlExternally("https://goo.gl/forms/QACEP8pP3RV60QYG2")
|
||||||
|
visible: backupListFooter.showInfoButton
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionButton
|
||||||
|
{
|
||||||
|
id: createBackupButton
|
||||||
|
text: catalog.i18nc("@button", "Backup Now")
|
||||||
|
iconSource: "../images/backup.svg"
|
||||||
|
enabled: !CuraDrive.isCreatingBackup && !CuraDrive.isRestoringBackup
|
||||||
|
onClicked: CuraDrive.createBackup()
|
||||||
|
busy: CuraDrive.isCreatingBackup
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionCheckBox
|
||||||
|
{
|
||||||
|
id: autoBackupEnabled
|
||||||
|
checked: CuraDrive.autoBackupEnabled
|
||||||
|
onClicked: CuraDrive.toggleAutoBackup(autoBackupEnabled.checked)
|
||||||
|
label: catalog.i18nc("@checkbox:description", "Automatically create a backup each day that Cura is started.")
|
||||||
|
}
|
||||||
|
}
|
112
plugins/CuraDrive/src/qml/components/BackupListItem.qml
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
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
|
||||||
|
|
||||||
|
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("default_margin").width * 2
|
||||||
|
width: parent.width
|
||||||
|
height: 50 * screenScaleFactor
|
||||||
|
|
||||||
|
ActionButton
|
||||||
|
{
|
||||||
|
color: "transparent"
|
||||||
|
hoverColor: "transparent"
|
||||||
|
textColor: UM.Theme.getColor("text")
|
||||||
|
textHoverColor: UM.Theme.getColor("primary")
|
||||||
|
iconSource: "../images/info.svg"
|
||||||
|
onClicked: backupListItem.showDetails = !backupListItem.showDetails
|
||||||
|
}
|
||||||
|
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: new Date(model["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
|
||||||
|
renderType: Text.NativeRendering
|
||||||
|
}
|
||||||
|
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: model["data"]["description"]
|
||||||
|
color: UM.Theme.getColor("text")
|
||||||
|
elide: Text.ElideRight
|
||||||
|
Layout.minimumWidth: 100 * screenScaleFactor
|
||||||
|
Layout.maximumWidth: 500 * screenScaleFactor
|
||||||
|
Layout.fillWidth: true
|
||||||
|
renderType: Text.NativeRendering
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionButton
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@button", "Restore")
|
||||||
|
color: "transparent"
|
||||||
|
hoverColor: "transparent"
|
||||||
|
textColor: UM.Theme.getColor("text")
|
||||||
|
textHoverColor: UM.Theme.getColor("text_link")
|
||||||
|
enabled: !CuraDrive.isCreatingBackup && !CuraDrive.isRestoringBackup
|
||||||
|
onClicked: confirmRestoreDialog.visible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionButton
|
||||||
|
{
|
||||||
|
color: "transparent"
|
||||||
|
hoverColor: "transparent"
|
||||||
|
textColor: UM.Theme.getColor("setting_validation_error")
|
||||||
|
textHoverColor: UM.Theme.getColor("setting_validation_error")
|
||||||
|
iconSource: "../images/delete.svg"
|
||||||
|
onClicked: confirmDeleteDialog.visible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BackupListItemDetails
|
||||||
|
{
|
||||||
|
id: backupDetails
|
||||||
|
backupDetailsData: model
|
||||||
|
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(model["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(model["backup_id"])
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
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: 10 * screenScaleFactor
|
||||||
|
property var backupDetailsData
|
||||||
|
|
||||||
|
// Cura version
|
||||||
|
BackupListItemDetailsRow
|
||||||
|
{
|
||||||
|
iconSource: "../images/cura.svg"
|
||||||
|
label: catalog.i18nc("@backuplist:label", "Cura Version")
|
||||||
|
value: backupDetailsData["data"]["cura_release"]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Machine count.
|
||||||
|
BackupListItemDetailsRow
|
||||||
|
{
|
||||||
|
iconSource: "../images/printer.svg"
|
||||||
|
label: catalog.i18nc("@backuplist:label", "Machines")
|
||||||
|
value: backupDetailsData["data"]["machine_count"]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Meterial count.
|
||||||
|
BackupListItemDetailsRow
|
||||||
|
{
|
||||||
|
iconSource: "../images/material.svg"
|
||||||
|
label: catalog.i18nc("@backuplist:label", "Materials")
|
||||||
|
value: backupDetailsData["data"]["material_count"]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Meterial count.
|
||||||
|
BackupListItemDetailsRow
|
||||||
|
{
|
||||||
|
iconSource: "../images/profile.svg"
|
||||||
|
label: catalog.i18nc("@backuplist:label", "Profiles")
|
||||||
|
value: backupDetailsData["data"]["profile_count"]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Meterial count.
|
||||||
|
BackupListItemDetailsRow
|
||||||
|
{
|
||||||
|
iconSource: "../images/plugin.svg"
|
||||||
|
label: catalog.i18nc("@backuplist:label", "Plugins")
|
||||||
|
value: backupDetailsData["data"]["plugin_count"]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spacer.
|
||||||
|
Item
|
||||||
|
{
|
||||||
|
width: parent.width
|
||||||
|
height: 10 * screenScaleFactor
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
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 var iconSource
|
||||||
|
property var label
|
||||||
|
property var value
|
||||||
|
|
||||||
|
// Spacing.
|
||||||
|
Item
|
||||||
|
{
|
||||||
|
width: 40 * screenScaleFactor
|
||||||
|
}
|
||||||
|
|
||||||
|
Icon
|
||||||
|
{
|
||||||
|
width: 18 * screenScaleFactor
|
||||||
|
iconSource: detailsRow.iconSource
|
||||||
|
color: UM.Theme.getColor("text")
|
||||||
|
}
|
||||||
|
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: detailsRow.label
|
||||||
|
color: UM.Theme.getColor("text")
|
||||||
|
elide: Text.ElideRight
|
||||||
|
Layout.minimumWidth: 50 * screenScaleFactor
|
||||||
|
Layout.maximumWidth: 100 * screenScaleFactor
|
||||||
|
Layout.fillWidth: true
|
||||||
|
renderType: Text.NativeRendering
|
||||||
|
}
|
||||||
|
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: detailsRow.value
|
||||||
|
color: UM.Theme.getColor("text")
|
||||||
|
elide: Text.ElideRight
|
||||||
|
Layout.minimumWidth: 50 * screenScaleFactor
|
||||||
|
Layout.maximumWidth: 100 * screenScaleFactor
|
||||||
|
Layout.fillWidth: true
|
||||||
|
renderType: Text.NativeRendering
|
||||||
|
}
|
||||||
|
}
|
11
plugins/CuraDrive/src/qml/components/Divider.qml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
import QtQuick 2.7
|
||||||
|
|
||||||
|
import UM 1.3 as UM
|
||||||
|
|
||||||
|
Rectangle
|
||||||
|
{
|
||||||
|
id: divider
|
||||||
|
color: UM.Theme.getColor("lining")
|
||||||
|
height: UM.Theme.getSize("default_lining").height
|
||||||
|
}
|
56
plugins/CuraDrive/src/qml/components/Icon.qml
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
import QtQuick 2.7
|
||||||
|
import QtQuick.Controls 2.1
|
||||||
|
import QtGraphicalEffects 1.0
|
||||||
|
|
||||||
|
Item
|
||||||
|
{
|
||||||
|
id: icon
|
||||||
|
width: parent.height
|
||||||
|
height: width
|
||||||
|
property var color: "transparent"
|
||||||
|
property var iconSource
|
||||||
|
property bool animated: false
|
||||||
|
|
||||||
|
Image
|
||||||
|
{
|
||||||
|
id: iconImage
|
||||||
|
width: parent.height
|
||||||
|
height: width
|
||||||
|
smooth: true
|
||||||
|
source: icon.iconSource
|
||||||
|
sourceSize.width: width
|
||||||
|
sourceSize.height: height
|
||||||
|
antialiasing: true
|
||||||
|
visible: !icon.animated
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedImage
|
||||||
|
{
|
||||||
|
id: animatedIconImage
|
||||||
|
width: parent.height
|
||||||
|
height: width
|
||||||
|
smooth: true
|
||||||
|
antialiasing: true
|
||||||
|
source: "../images/loading.gif"
|
||||||
|
visible: icon.animated
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorOverlay
|
||||||
|
{
|
||||||
|
anchors.fill: iconImage
|
||||||
|
source: iconImage
|
||||||
|
color: icon.color
|
||||||
|
antialiasing: true
|
||||||
|
visible: !icon.animated
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorOverlay
|
||||||
|
{
|
||||||
|
anchors.fill: animatedIconImage
|
||||||
|
source: animatedIconImage
|
||||||
|
color: icon.color
|
||||||
|
antialiasing: true
|
||||||
|
visible: icon.animated
|
||||||
|
}
|
||||||
|
}
|
13
plugins/CuraDrive/src/qml/components/RightSideScrollBar.qml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
import QtQuick 2.7
|
||||||
|
import QtQuick.Controls 2.1
|
||||||
|
import QtQuick.Layouts 1.3
|
||||||
|
|
||||||
|
ScrollBar
|
||||||
|
{
|
||||||
|
active: true
|
||||||
|
size: parent.height
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
}
|
BIN
plugins/CuraDrive/src/qml/images/avatar_default.png
Normal file
After Width: | Height: | Size: 3 KiB |
12
plugins/CuraDrive/src/qml/images/background.svg
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg width="1024px" height="1183px" viewBox="0 0 1024 1183" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 39.1 (31720) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>Polygon 18</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Home" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" fill-opacity="0.2">
|
||||||
|
<g id="1440px-home" transform="translate(-86.000000, -30.000000)" fill="#D1D9DB">
|
||||||
|
<polygon id="Polygon-18" points="598 30 1110 325.603338 1110 916.810013 598 1212.41335 86 916.810013 86 325.603338"></polygon>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 734 B |
3
plugins/CuraDrive/src/qml/images/backup.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="white">
|
||||||
|
<path d="M16.5 11.3h-5.2v5.2H8.7v-5.2H3.5V8.7h5.2V3.5h2.6v5.2h5.2z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 154 B |
7
plugins/CuraDrive/src/qml/images/cura.svg
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30">
|
||||||
|
<switch>
|
||||||
|
<g>
|
||||||
|
<path d="M11.07 3L3 11.071V27h15.931L27 18.93V3H11.07zm10.175 8.235h-6.071c-2.02.013-3.016 1.414-3.016 3.115 0 1.702.996 3.125 3.016 3.136h6.071v3.433h-6.071c-3.996 0-6.419-2.743-6.419-6.568 0-3.826 2.423-6.548 6.419-6.548h6.071v3.432z"/>
|
||||||
|
</g>
|
||||||
|
</switch>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 370 B |
BIN
plugins/CuraDrive/src/qml/images/cura_logo.jpg
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
plugins/CuraDrive/src/qml/images/cura_logo.png
Normal file
After Width: | Height: | Size: 13 KiB |
7
plugins/CuraDrive/src/qml/images/delete.svg
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 13" fill="red">
|
||||||
|
<switch>
|
||||||
|
<g>
|
||||||
|
<path d="M13 2.23L8.73 6.5 13 10.77l-2.135 2.134-4.269-4.269-4.27 4.269L.191 10.77l4.27-4.27-4.27-4.27L2.326.096l4.27 4.269L10.865.096z"/>
|
||||||
|
</g>
|
||||||
|
</switch>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 281 B |
7
plugins/CuraDrive/src/qml/images/folder.svg
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30">
|
||||||
|
<switch>
|
||||||
|
<g>
|
||||||
|
<path d="M21.718 10.969V7.758H8.451L7.014 5.222h-5.83v19.436h21.211l6.422-13.69-7.099.001zm-1.098 0H8.958L3.043 23.56h-.761V6.321h4.056l1.437 2.535H20.62v2.113z"/>
|
||||||
|
</g>
|
||||||
|
</switch>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 295 B |
3
plugins/CuraDrive/src/qml/images/home.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10" fill="white">
|
||||||
|
<path d="m 8.5727081,5.62578 v 2.97725 q 0,0.16127 -0.1178498,0.27912 Q 8.3370085,9 8.1757405,9 H 5.7939349 V 6.61819 H 4.2060646 V 9 H 1.824259 Q 1.6629909,9 1.5451412,8.88215 1.4272914,8.7643 1.4272914,8.60303 V 5.62578 q 0,-0.006 0.0031,-0.0186 0.0031,-0.0124 0.0031,-0.0186 L 4.9999998,2.64852 8.5665054,5.58856 q 0.0062,0.0124 0.0062,0.0372 z M 9.955892,5.19779 9.5713297,5.65679 q -0.049621,0.0558 -0.130255,0.0682 h -0.018608 q -0.080634,0 -0.130255,-0.0434 L 4.9999998,2.10269 0.70778771,5.6816 Q 0.63335631,5.7312 0.55892486,5.725 0.47829087,5.7126 0.42866987,5.6568 L 0.04410752,5.1978 Q -0.00551343,5.1358 6.8917799e-4,5.05204 0.00689178,4.96834 0.06891799,4.91869 L 4.5286008,1.20331 q 0.1984838,-0.16127 0.471399,-0.16127 0.2729153,0 0.471399,0.16127 L 6.9848377,2.46864 V 1.25913 q 0,-0.0868 0.055824,-0.14266 0.055824,-0.0558 0.1426602,-0.0558 h 1.1909028 q 0.086837,0 0.1426602,0.0558 0.055824,0.0558 0.055824,0.14266 V 3.7898 l 1.3583734,1.12888 q 0.062026,0.0496 0.068229,0.13335 0.0062,0.0837 -0.043418,0.14576 z" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
BIN
plugins/CuraDrive/src/qml/images/icon.png
Normal file
After Width: | Height: | Size: 21 KiB |
4
plugins/CuraDrive/src/qml/images/info.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="15" height="15" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M7.5 15C11.641 15 15 11.643 15 7.5 15 3.358 11.641 0 7.5 0 3.358 0 0 3.358 0 7.5 0 11.643 3.358 15 7.5 15ZM8.6 12.369L6.472 12.369 6.472 4.57 8.6 4.57 8.6 12.369ZM7.541 1.514C8.313 1.514 8.697 1.861 8.697 2.553 8.697 2.885 8.6 3.141 8.409 3.325 8.216 3.509 7.926 3.601 7.541 3.601 6.767 3.601 6.382 3.252 6.382 2.553 6.382 1.861 6.767 1.514 7.541 1.514Z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 499 B |
BIN
plugins/CuraDrive/src/qml/images/inverted_circle.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
plugins/CuraDrive/src/qml/images/loading.gif
Normal file
After Width: | Height: | Size: 6.6 KiB |
7
plugins/CuraDrive/src/qml/images/material.svg
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30">
|
||||||
|
<switch>
|
||||||
|
<g>
|
||||||
|
<path d="M2.995 1H5.67v24H2.995zm21.33 0H27v24h-2.675zM8.992 3.284c-.368 0-.669.224-.669.5v18.433c0 .276.3.5.669.5.369 0 .669-.224.669-.5V3.784c0-.276-.299-.5-.669-.5m4.003 0c-.368 0-.669.224-.669.5v18.433c0 .276.3.5.669.5.371 0 .669-.224.669-.5V3.784c0-.276-.298-.5-.669-.5m4.004 0c-.371 0-.669.224-.669.5v24.451c0 .277.298.5.669.5.368 0 .669-.223.669-.5V3.784c0-.276-.301-.5-.669-.5m4.003 0c-.368 0-.669.224-.669.5v18.433c0 .276.3.5.669.5.37 0 .669-.224.669-.5V3.784c-.001-.276-.3-.5-.669-.5"/>
|
||||||
|
</g>
|
||||||
|
</switch>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 628 B |
7
plugins/CuraDrive/src/qml/images/plugin.svg
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30">
|
||||||
|
<switch>
|
||||||
|
<g>
|
||||||
|
<path d="M22.32 19.715l-6.96-6.96 6.96-6.958v4.541H27V3H10.999L3 11.001V27h15.998L27 19.001v-3.828h-4.68z"/>
|
||||||
|
</g>
|
||||||
|
</switch>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 240 B |
BIN
plugins/CuraDrive/src/qml/images/preview_banner.png
Normal file
After Width: | Height: | Size: 8.1 KiB |
14
plugins/CuraDrive/src/qml/images/printer.svg
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 49 (51002) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>icn_singlePrinter</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Visual" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Printer-status-icon" transform="translate(-217.000000, -176.000000)" fill="#000000">
|
||||||
|
<g id="icn_singlePrinter" transform="translate(217.000000, 176.000000)">
|
||||||
|
<path d="M2,13 L14,13 L14,15 L2,15 L2,13 L2,13 Z M2,3 L14,3 L14,5 L2,5 L2,3 L2,3 Z M0,14 L2,14 L2,16 L0,16 L0,14 L0,14 Z M14,14 L16,14 L16,16 L14,16 L14,14 L14,14 Z M0,0 L2,0 L2,14 L0,14 L0,0 L0,0 Z M14,0 L16,0 L16,14 L14,14 L14,0 L14,0 Z M6,5 L10,5 L10,6 L6,6 L6,5 L6,5 Z M7,6 L9,6 L9,8 L7,8 L7,6 L7,6 Z M2,0 L14,0 L14,2 L2,2 L2,0 L2,0 Z M3,12 L13,12 L13,13 L3,13 L3,12 L3,12 Z" id="Rectangle-185-Copy-5-Copy-4"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
3
plugins/CuraDrive/src/qml/images/profile.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 13">
|
||||||
|
<path d="M12.96 5.778c-.021-.182-.234-.32-.419-.32-.595 0-1.124-.35-1.346-.89a1.45 1.45 0 0 1 .364-1.608.361.361 0 0 0 .04-.49 6.43 6.43 0 0 0-1.03-1.04.362.362 0 0 0-.494.04c-.388.43-1.084.589-1.621.364A1.444 1.444 0 0 1 7.576.423a.36.36 0 0 0-.32-.38A6.485 6.485 0 0 0 5.795.04a.362.362 0 0 0-.321.372 1.447 1.447 0 0 1-.89 1.387c-.532.217-1.223.06-1.61-.366a.362.362 0 0 0-.49-.041A6.46 6.46 0 0 0 1.43 2.43a.362.362 0 0 0 .04.493 1.44 1.44 0 0 1 .363 1.622c-.225.534-.78.879-1.415.879a.354.354 0 0 0-.375.319A6.51 6.51 0 0 0 .04 7.222c.02.183.24.32.426.32a1.426 1.426 0 0 1 1.338.89c.227.554.08 1.2-.364 1.608a.36.36 0 0 0-.04.49c.303.384.65.734 1.029 1.04.149.12.365.103.495-.04.389-.43 1.084-.589 1.62-.364.561.235.914.802.88 1.41a.36.36 0 0 0 .318.38 6.44 6.44 0 0 0 1.463.005.362.362 0 0 0 .322-.373 1.445 1.445 0 0 1 .889-1.386c.535-.218 1.223-.058 1.61.366.128.14.34.157.49.041a6.476 6.476 0 0 0 1.052-1.04.361.361 0 0 0-.039-.493 1.44 1.44 0 0 1-.364-1.622c.22-.527.755-.88 1.33-.88l.08.001a.362.362 0 0 0 .38-.319c.058-.488.058-.985.003-1.478zM6.51 8.682a2.17 2.17 0 0 1-2.168-2.168A2.17 2.17 0 0 1 6.51 4.346a2.17 2.17 0 0 1 2.168 2.168A2.17 2.17 0 0 1 6.51 8.682z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
7
plugins/CuraDrive/src/qml/images/restore.svg
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250">
|
||||||
|
<switch>
|
||||||
|
<g>
|
||||||
|
<path d="M-.113 31.935c8.904 4.904 17.81 9.802 26.709 14.714 2.507 1.384 4.993 2.807 7.868 4.426C57.24 23.782 85.983 7.847 121.495 5.371c27.659-1.928 53.113 5.077 76.006 20.788 44.611 30.614 63.473 86.919 46.099 137.829-17.883 52.399-66.749 82.265-113.69 81.745v-36.688c25.195-1.141 46.785-10.612 63.364-30.267 11.82-14.013 18.14-30.322 19.349-48.541 2.323-34.992-19.005-68.519-51.909-81.916-32.223-13.12-70.379-4.319-93 22.01l31.263 18.198c-1.545 1.07-2.387 1.747-3.312 2.281a81656.22 81656.22 0 0 1-92.099 53.112c-1.128.65-2.448.967-3.679 1.439V31.935z"/>
|
||||||
|
</g>
|
||||||
|
</switch>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 691 B |
42
plugins/CuraDrive/src/qml/main.qml
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
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: minimumWidth * 1.2
|
||||||
|
maximumHeight: minimumHeight * 1.2
|
||||||
|
width: minimumWidth
|
||||||
|
height: minimumHeight
|
||||||
|
color: UM.Theme.getColor("sidebar")
|
||||||
|
title: catalog.i18nc("@title:window", "Cura Backups")
|
||||||
|
|
||||||
|
// Globally available.
|
||||||
|
UM.I18nCatalog
|
||||||
|
{
|
||||||
|
id: catalog
|
||||||
|
name: "cura_drive"
|
||||||
|
}
|
||||||
|
|
||||||
|
WelcomePage
|
||||||
|
{
|
||||||
|
id: welcomePage
|
||||||
|
visible: !Cura.API.account.isLoggedIn
|
||||||
|
}
|
||||||
|
|
||||||
|
BackupsPage
|
||||||
|
{
|
||||||
|
id: backupsPage
|
||||||
|
visible: Cura.API.account.isLoggedIn
|
||||||
|
}
|
||||||
|
}
|
73
plugins/CuraDrive/src/qml/pages/BackupsPage.qml
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
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("default_margin").width * 3
|
||||||
|
|
||||||
|
ColumnLayout
|
||||||
|
{
|
||||||
|
spacing: UM.Theme.getSize("default_margin").height * 2
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
plugins/CuraDrive/src/qml/pages/WelcomePage.qml
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
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
|
||||||
|
topPadding: 150 * screenScaleFactor
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionButton
|
||||||
|
{
|
||||||
|
id: loginButton
|
||||||
|
onClicked: Cura.API.account.login()
|
||||||
|
text: catalog.i18nc("@button", "Sign In")
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
|
@ -203,7 +203,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def stopSlicing(self) -> None:
|
def stopSlicing(self) -> None:
|
||||||
self.backendStateChange.emit(BackendState.NotStarted)
|
self.setState(BackendState.NotStarted)
|
||||||
if self._slicing: # We were already slicing. Stop the old job.
|
if self._slicing: # We were already slicing. Stop the old job.
|
||||||
self._terminate()
|
self._terminate()
|
||||||
self._createSocket()
|
self._createSocket()
|
||||||
|
@ -322,7 +322,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
self._start_slice_job = None
|
self._start_slice_job = None
|
||||||
|
|
||||||
if job.isCancelled() or job.getError() or job.getResult() == StartJobResult.Error:
|
if job.isCancelled() or job.getError() or job.getResult() == StartJobResult.Error:
|
||||||
self.backendStateChange.emit(BackendState.Error)
|
self.setState(BackendState.Error)
|
||||||
self.backendError.emit(job)
|
self.backendError.emit(job)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -331,10 +331,10 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
self._error_message = Message(catalog.i18nc("@info:status",
|
self._error_message = Message(catalog.i18nc("@info:status",
|
||||||
"Unable to slice with the current material as it is incompatible with the selected machine or configuration."), title = catalog.i18nc("@info:title", "Unable to slice"))
|
"Unable to slice with the current material as it is incompatible with the selected machine or configuration."), title = catalog.i18nc("@info:title", "Unable to slice"))
|
||||||
self._error_message.show()
|
self._error_message.show()
|
||||||
self.backendStateChange.emit(BackendState.Error)
|
self.setState(BackendState.Error)
|
||||||
self.backendError.emit(job)
|
self.backendError.emit(job)
|
||||||
else:
|
else:
|
||||||
self.backendStateChange.emit(BackendState.NotStarted)
|
self.setState(BackendState.NotStarted)
|
||||||
return
|
return
|
||||||
|
|
||||||
if job.getResult() == StartJobResult.SettingError:
|
if job.getResult() == StartJobResult.SettingError:
|
||||||
|
@ -362,10 +362,10 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice with the current settings. The following settings have errors: {0}").format(", ".join(error_labels)),
|
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice with the current settings. The following settings have errors: {0}").format(", ".join(error_labels)),
|
||||||
title = catalog.i18nc("@info:title", "Unable to slice"))
|
title = catalog.i18nc("@info:title", "Unable to slice"))
|
||||||
self._error_message.show()
|
self._error_message.show()
|
||||||
self.backendStateChange.emit(BackendState.Error)
|
self.setState(BackendState.Error)
|
||||||
self.backendError.emit(job)
|
self.backendError.emit(job)
|
||||||
else:
|
else:
|
||||||
self.backendStateChange.emit(BackendState.NotStarted)
|
self.setState(BackendState.NotStarted)
|
||||||
return
|
return
|
||||||
|
|
||||||
elif job.getResult() == StartJobResult.ObjectSettingError:
|
elif job.getResult() == StartJobResult.ObjectSettingError:
|
||||||
|
@ -386,7 +386,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice due to some per-model settings. The following settings have errors on one or more models: {error_labels}").format(error_labels = ", ".join(errors.values())),
|
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice due to some per-model settings. The following settings have errors on one or more models: {error_labels}").format(error_labels = ", ".join(errors.values())),
|
||||||
title = catalog.i18nc("@info:title", "Unable to slice"))
|
title = catalog.i18nc("@info:title", "Unable to slice"))
|
||||||
self._error_message.show()
|
self._error_message.show()
|
||||||
self.backendStateChange.emit(BackendState.Error)
|
self.setState(BackendState.Error)
|
||||||
self.backendError.emit(job)
|
self.backendError.emit(job)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -395,16 +395,16 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice because the prime tower or prime position(s) are invalid."),
|
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice because the prime tower or prime position(s) are invalid."),
|
||||||
title = catalog.i18nc("@info:title", "Unable to slice"))
|
title = catalog.i18nc("@info:title", "Unable to slice"))
|
||||||
self._error_message.show()
|
self._error_message.show()
|
||||||
self.backendStateChange.emit(BackendState.Error)
|
self.setState(BackendState.Error)
|
||||||
self.backendError.emit(job)
|
self.backendError.emit(job)
|
||||||
else:
|
else:
|
||||||
self.backendStateChange.emit(BackendState.NotStarted)
|
self.setState(BackendState.NotStarted)
|
||||||
|
|
||||||
if job.getResult() == StartJobResult.ObjectsWithDisabledExtruder:
|
if job.getResult() == StartJobResult.ObjectsWithDisabledExtruder:
|
||||||
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice because there are objects associated with disabled Extruder %s." % job.getMessage()),
|
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice because there are objects associated with disabled Extruder %s." % job.getMessage()),
|
||||||
title = catalog.i18nc("@info:title", "Unable to slice"))
|
title = catalog.i18nc("@info:title", "Unable to slice"))
|
||||||
self._error_message.show()
|
self._error_message.show()
|
||||||
self.backendStateChange.emit(BackendState.Error)
|
self.setState(BackendState.Error)
|
||||||
self.backendError.emit(job)
|
self.backendError.emit(job)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -413,10 +413,10 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
self._error_message = Message(catalog.i18nc("@info:status", "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit."),
|
self._error_message = Message(catalog.i18nc("@info:status", "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit."),
|
||||||
title = catalog.i18nc("@info:title", "Unable to slice"))
|
title = catalog.i18nc("@info:title", "Unable to slice"))
|
||||||
self._error_message.show()
|
self._error_message.show()
|
||||||
self.backendStateChange.emit(BackendState.Error)
|
self.setState(BackendState.Error)
|
||||||
self.backendError.emit(job)
|
self.backendError.emit(job)
|
||||||
else:
|
else:
|
||||||
self.backendStateChange.emit(BackendState.NotStarted)
|
self.setState(BackendState.NotStarted)
|
||||||
self._invokeSlice()
|
self._invokeSlice()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -424,7 +424,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
self._socket.sendMessage(job.getSliceMessage())
|
self._socket.sendMessage(job.getSliceMessage())
|
||||||
|
|
||||||
# Notify the user that it's now up to the backend to do it's job
|
# Notify the user that it's now up to the backend to do it's job
|
||||||
self.backendStateChange.emit(BackendState.Processing)
|
self.setState(BackendState.Processing)
|
||||||
|
|
||||||
if self._slice_start_time:
|
if self._slice_start_time:
|
||||||
Logger.log("d", "Sending slice message took %s seconds", time() - self._slice_start_time )
|
Logger.log("d", "Sending slice message took %s seconds", time() - self._slice_start_time )
|
||||||
|
@ -442,7 +442,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
||||||
if node.callDecoration("isBlockSlicing"):
|
if node.callDecoration("isBlockSlicing"):
|
||||||
enable_timer = False
|
enable_timer = False
|
||||||
self.backendStateChange.emit(BackendState.Disabled)
|
self.setState(BackendState.Disabled)
|
||||||
self._is_disabled = True
|
self._is_disabled = True
|
||||||
gcode_list = node.callDecoration("getGCodeList")
|
gcode_list = node.callDecoration("getGCodeList")
|
||||||
if gcode_list is not None:
|
if gcode_list is not None:
|
||||||
|
@ -451,7 +451,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
if self._use_timer == enable_timer:
|
if self._use_timer == enable_timer:
|
||||||
return self._use_timer
|
return self._use_timer
|
||||||
if enable_timer:
|
if enable_timer:
|
||||||
self.backendStateChange.emit(BackendState.NotStarted)
|
self.setState(BackendState.NotStarted)
|
||||||
self.enableTimer()
|
self.enableTimer()
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
|
@ -518,7 +518,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
self._build_plates_to_be_sliced.append(build_plate_number)
|
self._build_plates_to_be_sliced.append(build_plate_number)
|
||||||
self.printDurationMessage.emit(source_build_plate_number, {}, [])
|
self.printDurationMessage.emit(source_build_plate_number, {}, [])
|
||||||
self.processingProgress.emit(0.0)
|
self.processingProgress.emit(0.0)
|
||||||
self.backendStateChange.emit(BackendState.NotStarted)
|
self.setState(BackendState.NotStarted)
|
||||||
# if not self._use_timer:
|
# if not self._use_timer:
|
||||||
# With manually having to slice, we want to clear the old invalid layer data.
|
# With manually having to slice, we want to clear the old invalid layer data.
|
||||||
self._clearLayerData(build_plate_changed)
|
self._clearLayerData(build_plate_changed)
|
||||||
|
@ -567,7 +567,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
self.stopSlicing()
|
self.stopSlicing()
|
||||||
self.markSliceAll()
|
self.markSliceAll()
|
||||||
self.processingProgress.emit(0.0)
|
self.processingProgress.emit(0.0)
|
||||||
self.backendStateChange.emit(BackendState.NotStarted)
|
self.setState(BackendState.NotStarted)
|
||||||
if not self._use_timer:
|
if not self._use_timer:
|
||||||
# With manually having to slice, we want to clear the old invalid layer data.
|
# With manually having to slice, we want to clear the old invalid layer data.
|
||||||
self._clearLayerData()
|
self._clearLayerData()
|
||||||
|
@ -613,7 +613,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
# \param message The protobuf message containing the slicing progress.
|
# \param message The protobuf message containing the slicing progress.
|
||||||
def _onProgressMessage(self, message: Arcus.PythonMessage) -> None:
|
def _onProgressMessage(self, message: Arcus.PythonMessage) -> None:
|
||||||
self.processingProgress.emit(message.amount)
|
self.processingProgress.emit(message.amount)
|
||||||
self.backendStateChange.emit(BackendState.Processing)
|
self.setState(BackendState.Processing)
|
||||||
|
|
||||||
def _invokeSlice(self) -> None:
|
def _invokeSlice(self) -> None:
|
||||||
if self._use_timer:
|
if self._use_timer:
|
||||||
|
@ -632,7 +632,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
#
|
#
|
||||||
# \param message The protobuf message signalling that slicing is finished.
|
# \param message The protobuf message signalling that slicing is finished.
|
||||||
def _onSlicingFinishedMessage(self, message: Arcus.PythonMessage) -> None:
|
def _onSlicingFinishedMessage(self, message: Arcus.PythonMessage) -> None:
|
||||||
self.backendStateChange.emit(BackendState.Done)
|
self.setState(BackendState.Done)
|
||||||
self.processingProgress.emit(1.0)
|
self.processingProgress.emit(1.0)
|
||||||
|
|
||||||
gcode_list = self._scene.gcode_dict[self._start_slice_job_build_plate] #type: ignore #Because we generate this attribute dynamically.
|
gcode_list = self._scene.gcode_dict[self._start_slice_job_build_plate] #type: ignore #Because we generate this attribute dynamically.
|
||||||
|
|
|
@ -100,7 +100,7 @@ Item
|
||||||
source: UM.Theme.getIcon("load")
|
source: UM.Theme.getIcon("load")
|
||||||
width: UM.Theme.getSize("button_icon").width
|
width: UM.Theme.getSize("button_icon").width
|
||||||
height: UM.Theme.getSize("button_icon").height
|
height: UM.Theme.getSize("button_icon").height
|
||||||
color: UM.Theme.getColor("toolbar_button_text")
|
color: UM.Theme.getColor("icon")
|
||||||
|
|
||||||
sourceSize.height: height
|
sourceSize.height: height
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (c) 2017 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 os.path
|
import os.path
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
|
@ -15,9 +15,5 @@ class PrepareStage(CuraStage):
|
||||||
Application.getInstance().engineCreatedSignal.connect(self._engineCreated)
|
Application.getInstance().engineCreatedSignal.connect(self._engineCreated)
|
||||||
|
|
||||||
def _engineCreated(self):
|
def _engineCreated(self):
|
||||||
sidebar_component_path = os.path.join(Resources.getPath(Application.getInstance().ResourceTypes.QmlFiles),
|
|
||||||
"PrepareSidebar.qml")
|
|
||||||
|
|
||||||
menu_component_path = os.path.join(PluginRegistry.getInstance().getPluginPath("PrepareStage"), "PrepareMenu.qml")
|
menu_component_path = os.path.join(PluginRegistry.getInstance().getPluginPath("PrepareStage"), "PrepareMenu.qml")
|
||||||
self.addDisplayComponent("menu", menu_component_path)
|
self.addDisplayComponent("menu", menu_component_path)
|
||||||
self.addDisplayComponent("sidebar", sidebar_component_path)
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Cura is released under the terms of the LGPLv3 or higher.
|
// Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import QtQuick 2.7
|
import QtQuick 2.7
|
||||||
|
import QtQuick.Layouts 1.1
|
||||||
import QtQuick.Controls 2.3
|
import QtQuick.Controls 2.3
|
||||||
|
|
||||||
import UM 1.3 as UM
|
import UM 1.3 as UM
|
||||||
|
@ -19,12 +20,15 @@ Item
|
||||||
name: "cura"
|
name: "cura"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Row
|
Row
|
||||||
{
|
{
|
||||||
id: stageMenuRow
|
id: stageMenuRow
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
height: parent.height
|
height: parent.height
|
||||||
|
width: childrenRect.width
|
||||||
|
|
||||||
|
// We want this row to have a preferred with equals to the 85% of the parent
|
||||||
|
property int preferredWidth: Math.round(0.85 * previewMenu.width)
|
||||||
|
|
||||||
Cura.ViewsSelector
|
Cura.ViewsSelector
|
||||||
{
|
{
|
||||||
|
@ -45,11 +49,12 @@ Item
|
||||||
color: UM.Theme.getColor("lining")
|
color: UM.Theme.getColor("lining")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This component will grow freely up to complete the preferredWidth of the row.
|
||||||
Loader
|
Loader
|
||||||
{
|
{
|
||||||
id: viewPanel
|
id: viewPanel
|
||||||
height: parent.height
|
height: parent.height
|
||||||
width: childrenRect.width
|
width: source != "" ? (stageMenuRow.preferredWidth - viewsSelector.width - printSetupSelectorItem.width - 2 * UM.Theme.getSize("default_lining").width) : 0
|
||||||
source: UM.Controller.activeView != null && UM.Controller.activeView.stageMenuComponent != null ? UM.Controller.activeView.stageMenuComponent : ""
|
source: UM.Controller.activeView != null && UM.Controller.activeView.stageMenuComponent != null ? UM.Controller.activeView.stageMenuComponent : ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,6 @@ Cura.ExpandableComponent
|
||||||
{
|
{
|
||||||
id: base
|
id: base
|
||||||
|
|
||||||
width: UM.Theme.getSize("layerview_menu_size").width
|
|
||||||
contentHeaderTitle: catalog.i18nc("@label", "Color scheme")
|
contentHeaderTitle: catalog.i18nc("@label", "Color scheme")
|
||||||
|
|
||||||
Connections
|
Connections
|
||||||
|
@ -35,14 +34,36 @@ Cura.ExpandableComponent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
headerItem: Label
|
headerItem: Item
|
||||||
{
|
{
|
||||||
id: layerViewTypesLabel
|
Label
|
||||||
text: catalog.i18nc("@label", "Color scheme")
|
{
|
||||||
font: UM.Theme.getFont("default")
|
id: colorSchemeLabel
|
||||||
color: UM.Theme.getColor("setting_control_text")
|
text: catalog.i18nc("@label", "Color scheme")
|
||||||
height: base.height
|
verticalAlignment: Text.AlignVCenter
|
||||||
verticalAlignment: Text.AlignVCenter
|
height: parent.height
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font: UM.Theme.getFont("default")
|
||||||
|
color: UM.Theme.getColor("text_medium")
|
||||||
|
renderType: Text.NativeRendering
|
||||||
|
}
|
||||||
|
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: layerTypeCombobox.currentText
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
anchors
|
||||||
|
{
|
||||||
|
left: colorSchemeLabel.right
|
||||||
|
leftMargin: UM.Theme.getSize("default_margin").width
|
||||||
|
right: parent.right
|
||||||
|
}
|
||||||
|
height: parent.height
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font: UM.Theme.getFont("default")
|
||||||
|
color: UM.Theme.getColor("text")
|
||||||
|
renderType: Text.NativeRendering
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
contentItem: Column
|
contentItem: Column
|
||||||
|
@ -125,7 +146,7 @@ Cura.ExpandableComponent
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
id: compatibilityModeLabel
|
id: compatibilityModeLabel
|
||||||
text: catalog.i18nc("@label","Compatibility Mode")
|
text: catalog.i18nc("@label", "Compatibility Mode")
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
color: UM.Theme.getColor("text")
|
color: UM.Theme.getColor("text")
|
||||||
visible: UM.SimulationView.compatibilityMode
|
visible: UM.SimulationView.compatibilityMode
|
||||||
|
@ -136,7 +157,7 @@ Cura.ExpandableComponent
|
||||||
|
|
||||||
Item // Spacer
|
Item // Spacer
|
||||||
{
|
{
|
||||||
height: Math.round(UM.Theme.getSize("default_margin").width / 2)
|
height: UM.Theme.getSize("narrow_margin").width
|
||||||
width: width
|
width: width
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,17 +182,16 @@ Cura.ExpandableComponent
|
||||||
|
|
||||||
style: UM.Theme.styles.checkbox
|
style: UM.Theme.styles.checkbox
|
||||||
|
|
||||||
Rectangle
|
|
||||||
|
UM.RecolorImage
|
||||||
{
|
{
|
||||||
|
id: swatch
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.right: extrudersModelCheckBox.right
|
anchors.right: extrudersModelCheckBox.right
|
||||||
width: UM.Theme.getSize("layerview_legend_size").width
|
width: UM.Theme.getSize("layerview_legend_size").width
|
||||||
height: UM.Theme.getSize("layerview_legend_size").height
|
height: UM.Theme.getSize("layerview_legend_size").height
|
||||||
|
source: UM.Theme.getIcon("extruder_button")
|
||||||
color: model.color
|
color: model.color
|
||||||
radius: Math.round(width / 2)
|
|
||||||
border.width: UM.Theme.getSize("default_lining").width
|
|
||||||
border.color: UM.Theme.getColor("lining")
|
|
||||||
visible: !viewSettings.show_legend && !viewSettings.show_gradient
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Label
|
Label
|
||||||
|
@ -201,25 +221,25 @@ Cura.ExpandableComponent
|
||||||
Component.onCompleted:
|
Component.onCompleted:
|
||||||
{
|
{
|
||||||
typesLegendModel.append({
|
typesLegendModel.append({
|
||||||
label: catalog.i18nc("@label", "Show Travels"),
|
label: catalog.i18nc("@label", "Travels"),
|
||||||
initialValue: viewSettings.show_travel_moves,
|
initialValue: viewSettings.show_travel_moves,
|
||||||
preference: "layerview/show_travel_moves",
|
preference: "layerview/show_travel_moves",
|
||||||
colorId: "layerview_move_combing"
|
colorId: "layerview_move_combing"
|
||||||
});
|
});
|
||||||
typesLegendModel.append({
|
typesLegendModel.append({
|
||||||
label: catalog.i18nc("@label", "Show Helpers"),
|
label: catalog.i18nc("@label", "Helpers"),
|
||||||
initialValue: viewSettings.show_helpers,
|
initialValue: viewSettings.show_helpers,
|
||||||
preference: "layerview/show_helpers",
|
preference: "layerview/show_helpers",
|
||||||
colorId: "layerview_support"
|
colorId: "layerview_support"
|
||||||
});
|
});
|
||||||
typesLegendModel.append({
|
typesLegendModel.append({
|
||||||
label: catalog.i18nc("@label", "Show Shell"),
|
label: catalog.i18nc("@label", "Shell"),
|
||||||
initialValue: viewSettings.show_skin,
|
initialValue: viewSettings.show_skin,
|
||||||
preference: "layerview/show_skin",
|
preference: "layerview/show_skin",
|
||||||
colorId: "layerview_inset_0"
|
colorId: "layerview_inset_0"
|
||||||
});
|
});
|
||||||
typesLegendModel.append({
|
typesLegendModel.append({
|
||||||
label: catalog.i18nc("@label", "Show Infill"),
|
label: catalog.i18nc("@label", "Infill"),
|
||||||
initialValue: viewSettings.show_infill,
|
initialValue: viewSettings.show_infill,
|
||||||
preference: "layerview/show_infill",
|
preference: "layerview/show_infill",
|
||||||
colorId: "layerview_infill"
|
colorId: "layerview_infill"
|
||||||
|
|
|
@ -14,8 +14,8 @@ Window
|
||||||
modality: Qt.ApplicationModal
|
modality: Qt.ApplicationModal
|
||||||
flags: Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowCloseButtonHint
|
flags: Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowCloseButtonHint
|
||||||
|
|
||||||
width: 720 * screenScaleFactor
|
width: Math.floor(720 * screenScaleFactor)
|
||||||
height: 640 * screenScaleFactor
|
height: Math.floor(640 * screenScaleFactor)
|
||||||
minimumWidth: width
|
minimumWidth: width
|
||||||
maximumWidth: minimumWidth
|
maximumWidth: minimumWidth
|
||||||
minimumHeight: height
|
minimumHeight: height
|
||||||
|
@ -95,6 +95,7 @@ Window
|
||||||
licenseDialog.show();
|
licenseDialog.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolboxLicenseDialog
|
ToolboxLicenseDialog
|
||||||
{
|
{
|
||||||
id: licenseDialog
|
id: licenseDialog
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2018 Ultimaker B.V.
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import QtQuick 2.3
|
import QtQuick 2.10
|
||||||
import QtQuick.Controls 1.4
|
import QtQuick.Controls 1.4
|
||||||
import QtQuick.Controls.Styles 1.4
|
import QtQuick.Controls.Styles 1.4
|
||||||
import UM 1.1 as UM
|
import UM 1.1 as UM
|
||||||
|
@ -59,6 +59,7 @@ Item
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: UM.Theme.getSize("toolbox_property_label").height
|
height: UM.Theme.getSize("toolbox_property_label").height
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
|
@ -70,6 +71,7 @@ Item
|
||||||
left: title.left
|
left: title.left
|
||||||
topMargin: UM.Theme.getSize("default_margin").height
|
topMargin: UM.Theme.getSize("default_margin").height
|
||||||
}
|
}
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
Column
|
Column
|
||||||
{
|
{
|
||||||
|
@ -88,12 +90,14 @@ Item
|
||||||
text: catalog.i18nc("@label", "Website") + ":"
|
text: catalog.i18nc("@label", "Website") + ":"
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
color: UM.Theme.getColor("text_medium")
|
color: UM.Theme.getColor("text_medium")
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
text: catalog.i18nc("@label", "Email") + ":"
|
text: catalog.i18nc("@label", "Email") + ":"
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
color: UM.Theme.getColor("text_medium")
|
color: UM.Theme.getColor("text_medium")
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Column
|
Column
|
||||||
|
@ -122,6 +126,7 @@ Item
|
||||||
color: UM.Theme.getColor("text")
|
color: UM.Theme.getColor("text")
|
||||||
linkColor: UM.Theme.getColor("text_link")
|
linkColor: UM.Theme.getColor("text_link")
|
||||||
onLinkActivated: Qt.openUrlExternally(link)
|
onLinkActivated: Qt.openUrlExternally(link)
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
|
|
||||||
Label
|
Label
|
||||||
|
@ -138,6 +143,7 @@ Item
|
||||||
color: UM.Theme.getColor("text")
|
color: UM.Theme.getColor("text")
|
||||||
linkColor: UM.Theme.getColor("text_link")
|
linkColor: UM.Theme.getColor("text_link")
|
||||||
onLinkActivated: Qt.openUrlExternally(link)
|
onLinkActivated: Qt.openUrlExternally(link)
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Rectangle
|
Rectangle
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2018 Ultimaker B.V.
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import QtQuick 2.2
|
import QtQuick 2.10
|
||||||
import QtQuick.Controls 1.4
|
import QtQuick.Controls 1.4
|
||||||
import QtQuick.Controls.Styles 1.4
|
import QtQuick.Controls.Styles 1.4
|
||||||
import UM 1.1 as UM
|
import UM 1.1 as UM
|
||||||
|
@ -64,6 +64,7 @@ Item
|
||||||
font: UM.Theme.getFont("default_bold")
|
font: UM.Theme.getFont("default_bold")
|
||||||
horizontalAlignment: Text.AlignRight
|
horizontalAlignment: Text.AlignRight
|
||||||
width: control.width
|
width: control.width
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2018 Ultimaker B.V.
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import QtQuick 2.7
|
import QtQuick 2.10
|
||||||
import QtQuick.Controls 1.4
|
import QtQuick.Controls 1.4
|
||||||
import QtQuick.Controls.Styles 1.4
|
import QtQuick.Controls.Styles 1.4
|
||||||
import UM 1.1 as UM
|
import UM 1.1 as UM
|
||||||
|
@ -67,6 +67,7 @@ Item
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
color: UM.Theme.getColor("text_medium")
|
color: UM.Theme.getColor("text_medium")
|
||||||
font: UM.Theme.getFont("medium")
|
font: UM.Theme.getFont("medium")
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
|
|
||||||
TableView
|
TableView
|
||||||
|
@ -99,6 +100,7 @@ Item
|
||||||
text: styleData.value || ""
|
text: styleData.value || ""
|
||||||
color: UM.Theme.getColor("text")
|
color: UM.Theme.getColor("text")
|
||||||
font: UM.Theme.getFont("default_bold")
|
font: UM.Theme.getFont("default_bold")
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
Rectangle
|
Rectangle
|
||||||
{
|
{
|
||||||
|
@ -118,6 +120,7 @@ Item
|
||||||
text: styleData.value || ""
|
text: styleData.value || ""
|
||||||
color: UM.Theme.getColor("text_medium")
|
color: UM.Theme.getColor("text_medium")
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
itemDelegate: Item
|
itemDelegate: Item
|
||||||
|
@ -130,6 +133,7 @@ Item
|
||||||
text: styleData.value || ""
|
text: styleData.value || ""
|
||||||
color: UM.Theme.getColor("text_medium")
|
color: UM.Theme.getColor("text_medium")
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,6 +148,7 @@ Item
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
color: UM.Theme.getColor("text_medium")
|
color: UM.Theme.getColor("text_medium")
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,5 +237,6 @@ Item
|
||||||
color: UM.Theme.getColor("text")
|
color: UM.Theme.getColor("text")
|
||||||
linkColor: UM.Theme.getColor("text_link")
|
linkColor: UM.Theme.getColor("text_link")
|
||||||
onLinkActivated: Qt.openUrlExternally(link)
|
onLinkActivated: Qt.openUrlExternally(link)
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// 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 QtQuick 2.2
|
import QtQuick 2.10
|
||||||
import QtQuick.Controls 1.1
|
import QtQuick.Controls 1.1
|
||||||
import QtQuick.Controls.Styles 1.1
|
import QtQuick.Controls.Styles 1.1
|
||||||
import QtQuick.Layouts 1.1
|
import QtQuick.Layouts 1.1
|
||||||
|
@ -66,6 +66,7 @@ UM.Dialog
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
|
|
||||||
// Buttons
|
// Buttons
|
||||||
|
|
|
@ -26,10 +26,19 @@ Item
|
||||||
}
|
}
|
||||||
height: childrenRect.height + 2 * UM.Theme.getSize("wide_margin").height
|
height: childrenRect.height + 2 * UM.Theme.getSize("wide_margin").height
|
||||||
spacing: UM.Theme.getSize("default_margin").height
|
spacing: UM.Theme.getSize("default_margin").height
|
||||||
|
|
||||||
Repeater
|
Repeater
|
||||||
{
|
{
|
||||||
model: toolbox.packagesModel
|
model: toolbox.packagesModel
|
||||||
delegate: ToolboxDetailTile {}
|
delegate: Loader
|
||||||
|
{
|
||||||
|
// FIXME: When using asynchronous loading, on Mac and Windows, the tile may fail to load complete,
|
||||||
|
// leaving an empty space below the title part. We turn it off for now to make it work on Mac and
|
||||||
|
// Windows.
|
||||||
|
// Can be related to this QT bug: https://bugreports.qt.io/browse/QTBUG-50992
|
||||||
|
asynchronous: false
|
||||||
|
source: "ToolboxDetailTile.qml"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2018 Ultimaker B.V.
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import QtQuick 2.3
|
import QtQuick 2.10
|
||||||
import QtQuick.Controls 1.4
|
import QtQuick.Controls 1.4
|
||||||
import QtQuick.Controls.Styles 1.4
|
import QtQuick.Controls.Styles 1.4
|
||||||
import UM 1.1 as UM
|
import UM 1.1 as UM
|
||||||
|
@ -65,6 +65,7 @@ Item
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: UM.Theme.getSize("toolbox_property_label").height
|
height: UM.Theme.getSize("toolbox_property_label").height
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
|
|
||||||
Column
|
Column
|
||||||
|
@ -84,24 +85,28 @@ Item
|
||||||
text: catalog.i18nc("@label", "Version") + ":"
|
text: catalog.i18nc("@label", "Version") + ":"
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
color: UM.Theme.getColor("text_medium")
|
color: UM.Theme.getColor("text_medium")
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
text: catalog.i18nc("@label", "Last updated") + ":"
|
text: catalog.i18nc("@label", "Last updated") + ":"
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
color: UM.Theme.getColor("text_medium")
|
color: UM.Theme.getColor("text_medium")
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
text: catalog.i18nc("@label", "Author") + ":"
|
text: catalog.i18nc("@label", "Author") + ":"
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
color: UM.Theme.getColor("text_medium")
|
color: UM.Theme.getColor("text_medium")
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
text: catalog.i18nc("@label", "Downloads") + ":"
|
text: catalog.i18nc("@label", "Downloads") + ":"
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
color: UM.Theme.getColor("text_medium")
|
color: UM.Theme.getColor("text_medium")
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Column
|
Column
|
||||||
|
@ -121,6 +126,7 @@ Item
|
||||||
text: details === null ? "" : (details.version || catalog.i18nc("@label", "Unknown"))
|
text: details === null ? "" : (details.version || catalog.i18nc("@label", "Unknown"))
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
color: UM.Theme.getColor("text")
|
color: UM.Theme.getColor("text")
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
|
@ -135,6 +141,7 @@ Item
|
||||||
}
|
}
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
color: UM.Theme.getColor("text")
|
color: UM.Theme.getColor("text")
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
|
@ -153,12 +160,14 @@ Item
|
||||||
color: UM.Theme.getColor("text")
|
color: UM.Theme.getColor("text")
|
||||||
linkColor: UM.Theme.getColor("text_link")
|
linkColor: UM.Theme.getColor("text_link")
|
||||||
onLinkActivated: Qt.openUrlExternally(link)
|
onLinkActivated: Qt.openUrlExternally(link)
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
text: details === null ? "" : (details.download_count || catalog.i18nc("@label", "Unknown"))
|
text: details === null ? "" : (details.download_count || catalog.i18nc("@label", "Unknown"))
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
color: UM.Theme.getColor("text")
|
color: UM.Theme.getColor("text")
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Rectangle
|
Rectangle
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2018 Ultimaker B.V.
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import QtQuick 2.7
|
import QtQuick 2.10
|
||||||
import QtQuick.Controls 1.4
|
import QtQuick.Controls 1.4
|
||||||
import QtQuick.Controls.Styles 1.4
|
import QtQuick.Controls.Styles 1.4
|
||||||
import UM 1.1 as UM
|
import UM 1.1 as UM
|
||||||
|
@ -31,6 +31,7 @@ Item
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
color: UM.Theme.getColor("text")
|
color: UM.Theme.getColor("text")
|
||||||
font: UM.Theme.getFont("medium_bold")
|
font: UM.Theme.getFont("medium_bold")
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
|
@ -42,6 +43,7 @@ Item
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
color: UM.Theme.getColor("text")
|
color: UM.Theme.getColor("text")
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,40 +1,69 @@
|
||||||
// Copyright (c) 2018 Ultimaker B.V.
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import QtQuick 2.7
|
import QtQuick 2.10
|
||||||
import QtQuick.Controls 1.4
|
import QtQuick.Controls 1.4
|
||||||
import QtQuick.Controls.Styles 1.4
|
import QtQuick.Controls.Styles 1.4
|
||||||
import UM 1.1 as UM
|
import UM 1.1 as UM
|
||||||
|
import Cura 1.1 as Cura
|
||||||
|
|
||||||
Column
|
Column
|
||||||
{
|
{
|
||||||
property bool installed: toolbox.isInstalled(model.id)
|
property bool installed: toolbox.isInstalled(model.id)
|
||||||
property bool canUpdate: toolbox.canUpdate(model.id)
|
property bool canUpdate: toolbox.canUpdate(model.id)
|
||||||
|
property bool loginRequired: model.login_required && !Cura.API.account.isLoggedIn
|
||||||
|
|
||||||
width: UM.Theme.getSize("toolbox_action_button").width
|
width: UM.Theme.getSize("toolbox_action_button").width
|
||||||
spacing: UM.Theme.getSize("narrow_margin").height
|
spacing: UM.Theme.getSize("narrow_margin").height
|
||||||
|
|
||||||
ToolboxProgressButton
|
Item
|
||||||
{
|
{
|
||||||
id: installButton
|
width: installButton.width
|
||||||
active: toolbox.isDownloading && toolbox.activePackage == model
|
height: installButton.height
|
||||||
complete: installed
|
ToolboxProgressButton
|
||||||
readyAction: function()
|
|
||||||
{
|
{
|
||||||
toolbox.activePackage = model
|
id: installButton
|
||||||
toolbox.startDownload(model.download_url)
|
active: toolbox.isDownloading && toolbox.activePackage == model
|
||||||
|
onReadyAction:
|
||||||
|
{
|
||||||
|
toolbox.activePackage = model
|
||||||
|
toolbox.startDownload(model.download_url)
|
||||||
|
}
|
||||||
|
onActiveAction: toolbox.cancelDownload()
|
||||||
|
|
||||||
|
// Don't allow installing while another download is running
|
||||||
|
enabled: installed || (!(toolbox.isDownloading && toolbox.activePackage != model) && !loginRequired)
|
||||||
|
opacity: enabled ? 1.0 : 0.5
|
||||||
|
visible: !updateButton.visible && !installed// Don't show when the update button is visible
|
||||||
}
|
}
|
||||||
activeAction: function()
|
|
||||||
|
Cura.SecondaryButton
|
||||||
{
|
{
|
||||||
toolbox.cancelDownload()
|
visible: installed
|
||||||
|
onClicked: toolbox.viewCategory = "installed"
|
||||||
|
text: catalog.i18nc("@action:button", "Installed")
|
||||||
|
fixedWidthMode: true
|
||||||
|
width: installButton.width
|
||||||
|
height: installButton.height
|
||||||
}
|
}
|
||||||
completeAction: function()
|
}
|
||||||
|
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
text: catalog.i18nc("@label:The string between <a href=> and </a> is the highlighted link", "<a href='%1'>Log in</a> is required to install or update")
|
||||||
|
font: UM.Theme.getFont("default")
|
||||||
|
color: UM.Theme.getColor("text")
|
||||||
|
linkColor: UM.Theme.getColor("text_link")
|
||||||
|
visible: loginRequired
|
||||||
|
width: installButton.width
|
||||||
|
renderType: Text.NativeRendering
|
||||||
|
|
||||||
|
MouseArea
|
||||||
{
|
{
|
||||||
toolbox.viewCategory = "installed"
|
anchors.fill: parent
|
||||||
|
onClicked: Cura.API.account.login()
|
||||||
}
|
}
|
||||||
// Don't allow installing while another download is running
|
|
||||||
enabled: installed || !(toolbox.isDownloading && toolbox.activePackage != model)
|
|
||||||
opacity: enabled ? 1.0 : 0.5
|
|
||||||
visible: !updateButton.visible // Don't show when the update button is visible
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolboxProgressButton
|
ToolboxProgressButton
|
||||||
|
@ -44,20 +73,19 @@ Column
|
||||||
readyLabel: catalog.i18nc("@action:button", "Update")
|
readyLabel: catalog.i18nc("@action:button", "Update")
|
||||||
activeLabel: catalog.i18nc("@action:button", "Updating")
|
activeLabel: catalog.i18nc("@action:button", "Updating")
|
||||||
completeLabel: catalog.i18nc("@action:button", "Updated")
|
completeLabel: catalog.i18nc("@action:button", "Updated")
|
||||||
readyAction: function()
|
|
||||||
|
onReadyAction:
|
||||||
{
|
{
|
||||||
toolbox.activePackage = model
|
toolbox.activePackage = model
|
||||||
toolbox.update(model.id)
|
toolbox.update(model.id)
|
||||||
}
|
}
|
||||||
activeAction: function()
|
onActiveAction: toolbox.cancelDownload()
|
||||||
{
|
|
||||||
toolbox.cancelDownload()
|
|
||||||
}
|
|
||||||
// Don't allow installing while another download is running
|
// Don't allow installing while another download is running
|
||||||
enabled: !(toolbox.isDownloading && toolbox.activePackage != model)
|
enabled: !(toolbox.isDownloading && toolbox.activePackage != model) && !loginRequired
|
||||||
opacity: enabled ? 1.0 : 0.5
|
opacity: enabled ? 1.0 : 0.5
|
||||||
visible: canUpdate
|
visible: canUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections
|
Connections
|
||||||
{
|
{
|
||||||
target: toolbox
|
target: toolbox
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2018 Ultimaker B.V.
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import QtQuick 2.7
|
import QtQuick 2.10
|
||||||
import QtQuick.Controls 1.4
|
import QtQuick.Controls 1.4
|
||||||
import QtQuick.Controls.Styles 1.4
|
import QtQuick.Controls.Styles 1.4
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.3
|
||||||
|
@ -23,8 +23,9 @@ Column
|
||||||
width: parent.width
|
width: parent.width
|
||||||
color: UM.Theme.getColor("text_medium")
|
color: UM.Theme.getColor("text_medium")
|
||||||
font: UM.Theme.getFont("medium")
|
font: UM.Theme.getFont("medium")
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
GridLayout
|
Grid
|
||||||
{
|
{
|
||||||
id: grid
|
id: grid
|
||||||
width: parent.width - 2 * parent.padding
|
width: parent.width - 2 * parent.padding
|
||||||
|
@ -34,10 +35,12 @@ Column
|
||||||
Repeater
|
Repeater
|
||||||
{
|
{
|
||||||
model: gridArea.model
|
model: gridArea.model
|
||||||
delegate: ToolboxDownloadsGridTile
|
delegate: Loader
|
||||||
{
|
{
|
||||||
Layout.preferredWidth: (grid.width - (grid.columns - 1) * grid.columnSpacing) / grid.columns
|
asynchronous: true
|
||||||
Layout.preferredHeight: UM.Theme.getSize("toolbox_thumbnail_small").height
|
width: Math.round((grid.width - (grid.columns - 1) * grid.columnSpacing) / grid.columns)
|
||||||
|
height: UM.Theme.getSize("toolbox_thumbnail_small").height
|
||||||
|
source: "ToolboxDownloadsGridTile.qml"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2018 Ultimaker B.V.
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import QtQuick 2.3
|
import QtQuick 2.10
|
||||||
import QtQuick.Controls 1.4
|
import QtQuick.Controls 1.4
|
||||||
import QtQuick.Controls.Styles 1.4
|
import QtQuick.Controls.Styles 1.4
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.3
|
||||||
|
@ -62,6 +62,8 @@ Item
|
||||||
{
|
{
|
||||||
width: parent.width - thumbnail.width - parent.spacing
|
width: parent.width - thumbnail.width - parent.spacing
|
||||||
spacing: Math.floor(UM.Theme.getSize("narrow_margin").width)
|
spacing: Math.floor(UM.Theme.getSize("narrow_margin").width)
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
id: name
|
id: name
|
||||||
|
@ -70,6 +72,7 @@ Item
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
color: UM.Theme.getColor("text")
|
color: UM.Theme.getColor("text")
|
||||||
font: UM.Theme.getFont("default_bold")
|
font: UM.Theme.getFont("default_bold")
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
|
@ -81,6 +84,7 @@ Item
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
color: UM.Theme.getColor("text_medium")
|
color: UM.Theme.getColor("text_medium")
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2018 Ultimaker B.V.
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import QtQuick 2.7
|
import QtQuick 2.10
|
||||||
import QtQuick.Controls 1.4
|
import QtQuick.Controls 1.4
|
||||||
import QtQuick.Controls.Styles 1.4
|
import QtQuick.Controls.Styles 1.4
|
||||||
import UM 1.1 as UM
|
import UM 1.1 as UM
|
||||||
|
@ -24,29 +24,33 @@ Rectangle
|
||||||
width: parent.width
|
width: parent.width
|
||||||
color: UM.Theme.getColor("text_medium")
|
color: UM.Theme.getColor("text_medium")
|
||||||
font: UM.Theme.getFont("medium")
|
font: UM.Theme.getFont("medium")
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
Grid
|
Grid
|
||||||
{
|
{
|
||||||
height: childrenRect.height
|
height: childrenRect.height
|
||||||
spacing: UM.Theme.getSize("wide_margin").width
|
spacing: UM.Theme.getSize("wide_margin").width
|
||||||
columns: 3
|
columns: 3
|
||||||
anchors
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
{
|
|
||||||
horizontalCenter: parent.horizontalCenter
|
|
||||||
}
|
|
||||||
Repeater
|
Repeater
|
||||||
{
|
{
|
||||||
model: {
|
model:
|
||||||
if ( toolbox.viewCategory == "plugin" )
|
{
|
||||||
|
if (toolbox.viewCategory == "plugin")
|
||||||
{
|
{
|
||||||
return toolbox.pluginsShowcaseModel
|
return toolbox.pluginsShowcaseModel
|
||||||
}
|
}
|
||||||
if ( toolbox.viewCategory == "material" )
|
if (toolbox.viewCategory == "material")
|
||||||
{
|
{
|
||||||
return toolbox.materialsShowcaseModel
|
return toolbox.materialsShowcaseModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delegate: ToolboxDownloadsShowcaseTile {}
|
delegate: Loader
|
||||||
|
{
|
||||||
|
asynchronous: true
|
||||||
|
source: "ToolboxDownloadsShowcaseTile.qml"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// 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 QtQuick 2.7
|
import QtQuick 2.10
|
||||||
import QtQuick.Controls 1.4
|
import QtQuick.Controls 1.4
|
||||||
import QtQuick.Controls.Styles 1.4
|
import QtQuick.Controls.Styles 1.4
|
||||||
import QtGraphicalEffects 1.0
|
import QtGraphicalEffects 1.0
|
||||||
|
@ -79,6 +79,7 @@ Rectangle
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
color: UM.Theme.getColor("button_text")
|
color: UM.Theme.getColor("button_text")
|
||||||
font: UM.Theme.getFont("medium_bold")
|
font: UM.Theme.getFont("medium_bold")
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MouseArea
|
MouseArea
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2018 Ultimaker B.V.
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import QtQuick 2.7
|
import QtQuick 2.10
|
||||||
import QtQuick.Controls 1.4
|
import QtQuick.Controls 1.4
|
||||||
import QtQuick.Controls.Styles 1.4
|
import QtQuick.Controls.Styles 1.4
|
||||||
|
|
||||||
|
@ -18,5 +18,6 @@ Rectangle
|
||||||
{
|
{
|
||||||
centerIn: parent
|
centerIn: parent
|
||||||
}
|
}
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,24 @@
|
||||||
// Copyright (c) 2018 Ultimaker B.V.
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import QtQuick 2.2
|
import QtQuick 2.10
|
||||||
import QtQuick.Controls 1.4
|
import QtQuick.Controls 2.3
|
||||||
import QtQuick.Controls.Styles 1.4
|
|
||||||
import UM 1.1 as UM
|
import UM 1.1 as UM
|
||||||
|
import Cura 1.0 as Cura
|
||||||
|
|
||||||
Item
|
Item
|
||||||
{
|
{
|
||||||
id: footer
|
id: footer
|
||||||
width: parent.width
|
width: parent.width
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
height: visible ? Math.floor(UM.Theme.getSize("toolbox_footer").height) : 0
|
height: visible ? UM.Theme.getSize("toolbox_footer").height : 0
|
||||||
|
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
text: catalog.i18nc("@info", "You will need to restart Cura before changes in packages have effect.")
|
text: catalog.i18nc("@info", "You will need to restart Cura before changes in packages have effect.")
|
||||||
color: UM.Theme.getColor("text")
|
color: UM.Theme.getColor("text")
|
||||||
height: Math.floor(UM.Theme.getSize("toolbox_footer_button").height)
|
height: UM.Theme.getSize("toolbox_footer_button").height
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
anchors
|
anchors
|
||||||
{
|
{
|
||||||
|
@ -26,12 +28,12 @@ Item
|
||||||
right: restartButton.right
|
right: restartButton.right
|
||||||
rightMargin: UM.Theme.getSize("default_margin").width
|
rightMargin: UM.Theme.getSize("default_margin").width
|
||||||
}
|
}
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
Button
|
|
||||||
|
Cura.PrimaryButton
|
||||||
{
|
{
|
||||||
id: restartButton
|
id: restartButton
|
||||||
text: catalog.i18nc("@info:button", "Quit Cura")
|
|
||||||
anchors
|
anchors
|
||||||
{
|
{
|
||||||
top: parent.top
|
top: parent.top
|
||||||
|
@ -39,26 +41,11 @@ Item
|
||||||
right: parent.right
|
right: parent.right
|
||||||
rightMargin: UM.Theme.getSize("wide_margin").width
|
rightMargin: UM.Theme.getSize("wide_margin").width
|
||||||
}
|
}
|
||||||
iconName: "dialog-restart"
|
height: UM.Theme.getSize("toolbox_footer_button").height
|
||||||
|
text: catalog.i18nc("@info:button", "Quit Cura")
|
||||||
onClicked: toolbox.restart()
|
onClicked: toolbox.restart()
|
||||||
style: ButtonStyle
|
|
||||||
{
|
|
||||||
background: Rectangle
|
|
||||||
{
|
|
||||||
implicitWidth: UM.Theme.getSize("toolbox_footer_button").width
|
|
||||||
implicitHeight: Math.floor(UM.Theme.getSize("toolbox_footer_button").height)
|
|
||||||
color: control.hovered ? UM.Theme.getColor("primary_hover") : UM.Theme.getColor("primary")
|
|
||||||
}
|
|
||||||
label: Label
|
|
||||||
{
|
|
||||||
color: UM.Theme.getColor("button_text")
|
|
||||||
font: UM.Theme.getFont("default_bold")
|
|
||||||
text: control.text
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolboxShadow
|
ToolboxShadow
|
||||||
{
|
{
|
||||||
visible: footer.visible
|
visible: footer.visible
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2018 Ultimaker B.V.
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import QtQuick 2.7
|
import QtQuick 2.10
|
||||||
import QtQuick.Dialogs 1.1
|
import QtQuick.Dialogs 1.1
|
||||||
import QtQuick.Window 2.2
|
import QtQuick.Window 2.2
|
||||||
import QtQuick.Controls 1.4
|
import QtQuick.Controls 1.4
|
||||||
|
@ -21,44 +21,40 @@ ScrollView
|
||||||
Column
|
Column
|
||||||
{
|
{
|
||||||
spacing: UM.Theme.getSize("default_margin").height
|
spacing: UM.Theme.getSize("default_margin").height
|
||||||
|
visible: toolbox.pluginsInstalledModel.items.length > 0
|
||||||
|
height: childrenRect.height + 4 * UM.Theme.getSize("default_margin").height
|
||||||
|
|
||||||
anchors
|
anchors
|
||||||
{
|
{
|
||||||
right: parent.right
|
right: parent.right
|
||||||
left: parent.left
|
left: parent.left
|
||||||
leftMargin: UM.Theme.getSize("wide_margin").width
|
margins: UM.Theme.getSize("default_margin").width
|
||||||
topMargin: UM.Theme.getSize("wide_margin").height
|
|
||||||
bottomMargin: UM.Theme.getSize("wide_margin").height
|
|
||||||
top: parent.top
|
top: parent.top
|
||||||
}
|
}
|
||||||
height: childrenRect.height + 4 * UM.Theme.getSize("default_margin").height
|
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
visible: toolbox.pluginsInstalledModel.items.length > 0
|
width: page.width
|
||||||
width: parent.width
|
|
||||||
text: catalog.i18nc("@title:tab", "Plugins")
|
text: catalog.i18nc("@title:tab", "Plugins")
|
||||||
color: UM.Theme.getColor("text_medium")
|
color: UM.Theme.getColor("text_medium")
|
||||||
font: UM.Theme.getFont("medium")
|
font: UM.Theme.getFont("medium")
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
Rectangle
|
Rectangle
|
||||||
{
|
{
|
||||||
visible: toolbox.pluginsInstalledModel.items.length > 0
|
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: childrenRect.height + 1 * UM.Theme.getSize("default_lining").width
|
height: childrenRect.height + UM.Theme.getSize("default_margin").width
|
||||||
border.color: UM.Theme.getColor("lining")
|
border.color: UM.Theme.getColor("lining")
|
||||||
border.width: UM.Theme.getSize("default_lining").width
|
border.width: UM.Theme.getSize("default_lining").width
|
||||||
Column
|
Column
|
||||||
{
|
{
|
||||||
height: childrenRect.height
|
|
||||||
anchors
|
anchors
|
||||||
{
|
{
|
||||||
top: parent.top
|
top: parent.top
|
||||||
right: parent.right
|
right: parent.right
|
||||||
left: parent.left
|
left: parent.left
|
||||||
leftMargin: UM.Theme.getSize("default_margin").width
|
margins: UM.Theme.getSize("default_margin").width
|
||||||
rightMargin: UM.Theme.getSize("default_margin").width
|
|
||||||
topMargin: UM.Theme.getSize("default_lining").width
|
|
||||||
bottomMargin: UM.Theme.getSize("default_lining").width
|
|
||||||
}
|
}
|
||||||
Repeater
|
Repeater
|
||||||
{
|
{
|
||||||
|
@ -70,32 +66,27 @@ ScrollView
|
||||||
}
|
}
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
visible: toolbox.materialsInstalledModel.items.length > 0
|
|
||||||
width: page.width
|
|
||||||
text: catalog.i18nc("@title:tab", "Materials")
|
text: catalog.i18nc("@title:tab", "Materials")
|
||||||
color: UM.Theme.getColor("text_medium")
|
color: UM.Theme.getColor("text_medium")
|
||||||
font: UM.Theme.getFont("medium")
|
font: UM.Theme.getFont("medium")
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle
|
Rectangle
|
||||||
{
|
{
|
||||||
visible: toolbox.materialsInstalledModel.items.length > 0
|
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: childrenRect.height + 1 * UM.Theme.getSize("default_lining").width
|
height: childrenRect.height + UM.Theme.getSize("default_margin").width
|
||||||
border.color: UM.Theme.getColor("lining")
|
border.color: UM.Theme.getColor("lining")
|
||||||
border.width: UM.Theme.getSize("default_lining").width
|
border.width: UM.Theme.getSize("default_lining").width
|
||||||
Column
|
Column
|
||||||
{
|
{
|
||||||
height: Math.max( UM.Theme.getSize("wide_margin").height, childrenRect.height)
|
|
||||||
anchors
|
anchors
|
||||||
{
|
{
|
||||||
top: parent.top
|
top: parent.top
|
||||||
right: parent.right
|
right: parent.right
|
||||||
left: parent.left
|
left: parent.left
|
||||||
leftMargin: UM.Theme.getSize("default_margin").width
|
margins: UM.Theme.getSize("default_margin").width
|
||||||
rightMargin: UM.Theme.getSize("default_margin").width
|
|
||||||
topMargin: UM.Theme.getSize("default_lining").width
|
|
||||||
bottomMargin: UM.Theme.getSize("default_lining").width
|
|
||||||
}
|
}
|
||||||
Repeater
|
Repeater
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2018 Ultimaker B.V.
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import QtQuick 2.7
|
import QtQuick 2.10
|
||||||
import QtQuick.Controls 1.4
|
import QtQuick.Controls 1.4
|
||||||
import QtQuick.Controls.Styles 1.4
|
import QtQuick.Controls.Styles 1.4
|
||||||
import UM 1.1 as UM
|
import UM 1.1 as UM
|
||||||
|
@ -51,6 +51,7 @@ Item
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
font: UM.Theme.getFont("default_bold")
|
font: UM.Theme.getFont("default_bold")
|
||||||
color: pluginInfo.color
|
color: pluginInfo.color
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
|
@ -60,6 +61,7 @@ Item
|
||||||
width: parent.width
|
width: parent.width
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
color: pluginInfo.color
|
color: pluginInfo.color
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Column
|
Column
|
||||||
|
@ -88,6 +90,7 @@ Item
|
||||||
onLinkActivated: Qt.openUrlExternally("mailto:" + model.author_email + "?Subject=Cura: " + model.name + " Plugin")
|
onLinkActivated: Qt.openUrlExternally("mailto:" + model.author_email + "?Subject=Cura: " + model.name + " Plugin")
|
||||||
color: model.enabled ? UM.Theme.getColor("text") : UM.Theme.getColor("lining")
|
color: model.enabled ? UM.Theme.getColor("text") : UM.Theme.getColor("lining")
|
||||||
linkColor: UM.Theme.getColor("text_link")
|
linkColor: UM.Theme.getColor("text_link")
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
|
|
||||||
Label
|
Label
|
||||||
|
@ -98,6 +101,7 @@ Item
|
||||||
color: UM.Theme.getColor("text")
|
color: UM.Theme.getColor("text")
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
horizontalAlignment: Text.AlignLeft
|
horizontalAlignment: Text.AlignLeft
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ToolboxInstalledTileActions
|
ToolboxInstalledTileActions
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
// Copyright (c) 2018 Ultimaker B.V.
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import QtQuick 2.7
|
import QtQuick 2.10
|
||||||
import QtQuick.Controls 1.4
|
import QtQuick.Controls 1.4
|
||||||
import QtQuick.Controls.Styles 1.4
|
import QtQuick.Controls.Styles 1.4
|
||||||
import UM 1.1 as UM
|
import UM 1.1 as UM
|
||||||
|
|
||||||
|
import Cura 1.1 as Cura
|
||||||
|
|
||||||
Column
|
Column
|
||||||
{
|
{
|
||||||
property bool canUpdate: false
|
property bool canUpdate: false
|
||||||
property bool canDowngrade: false
|
property bool canDowngrade: false
|
||||||
|
property bool loginRequired: model.login_required && !Cura.API.account.isLoggedIn
|
||||||
width: UM.Theme.getSize("toolbox_action_button").width
|
width: UM.Theme.getSize("toolbox_action_button").width
|
||||||
spacing: UM.Theme.getSize("narrow_margin").height
|
spacing: UM.Theme.getSize("narrow_margin").height
|
||||||
|
|
||||||
|
@ -21,6 +24,7 @@ Column
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolboxProgressButton
|
ToolboxProgressButton
|
||||||
|
@ -30,59 +34,49 @@ Column
|
||||||
readyLabel: catalog.i18nc("@action:button", "Update")
|
readyLabel: catalog.i18nc("@action:button", "Update")
|
||||||
activeLabel: catalog.i18nc("@action:button", "Updating")
|
activeLabel: catalog.i18nc("@action:button", "Updating")
|
||||||
completeLabel: catalog.i18nc("@action:button", "Updated")
|
completeLabel: catalog.i18nc("@action:button", "Updated")
|
||||||
readyAction: function()
|
onReadyAction:
|
||||||
{
|
{
|
||||||
toolbox.activePackage = model
|
toolbox.activePackage = model
|
||||||
toolbox.update(model.id)
|
toolbox.update(model.id)
|
||||||
}
|
}
|
||||||
activeAction: function()
|
onActiveAction: toolbox.cancelDownload()
|
||||||
{
|
|
||||||
toolbox.cancelDownload()
|
|
||||||
}
|
|
||||||
// Don't allow installing while another download is running
|
// Don't allow installing while another download is running
|
||||||
enabled: !(toolbox.isDownloading && toolbox.activePackage != model)
|
enabled: !(toolbox.isDownloading && toolbox.activePackage != model) && !loginRequired
|
||||||
opacity: enabled ? 1.0 : 0.5
|
opacity: enabled ? 1.0 : 0.5
|
||||||
visible: canUpdate
|
visible: canUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
Button
|
Label
|
||||||
|
{
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
text: catalog.i18nc("@label:The string between <a href=> and </a> is the highlighted link", "<a href='%1'>Log in</a> is required to update")
|
||||||
|
font: UM.Theme.getFont("default")
|
||||||
|
color: UM.Theme.getColor("text")
|
||||||
|
linkColor: UM.Theme.getColor("text_link")
|
||||||
|
visible: loginRequired
|
||||||
|
width: updateButton.width
|
||||||
|
renderType: Text.NativeRendering
|
||||||
|
|
||||||
|
MouseArea
|
||||||
|
{
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: Cura.API.account.login()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Cura.SecondaryButton
|
||||||
{
|
{
|
||||||
id: removeButton
|
id: removeButton
|
||||||
text: canDowngrade ? catalog.i18nc("@action:button", "Downgrade") : catalog.i18nc("@action:button", "Uninstall")
|
text: canDowngrade ? catalog.i18nc("@action:button", "Downgrade") : catalog.i18nc("@action:button", "Uninstall")
|
||||||
visible: !model.is_bundled && model.is_installed
|
visible: !model.is_bundled && model.is_installed
|
||||||
enabled: !toolbox.isDownloading
|
enabled: !toolbox.isDownloading
|
||||||
style: ButtonStyle
|
|
||||||
{
|
width: UM.Theme.getSize("toolbox_action_button").width
|
||||||
background: Rectangle
|
height: UM.Theme.getSize("toolbox_action_button").height
|
||||||
{
|
|
||||||
implicitWidth: UM.Theme.getSize("toolbox_action_button").width
|
fixedWidthMode: true
|
||||||
implicitHeight: UM.Theme.getSize("toolbox_action_button").height
|
|
||||||
color: "transparent"
|
|
||||||
border
|
|
||||||
{
|
|
||||||
width: UM.Theme.getSize("default_lining").width
|
|
||||||
color:
|
|
||||||
{
|
|
||||||
if (control.hovered)
|
|
||||||
{
|
|
||||||
return UM.Theme.getColor("primary_hover")
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return UM.Theme.getColor("lining")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
label: Label
|
|
||||||
{
|
|
||||||
text: control.text
|
|
||||||
color: UM.Theme.getColor("text")
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
font: UM.Theme.getFont("default")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onClicked: toolbox.checkPackageUsageAndUninstall(model.id)
|
onClicked: toolbox.checkPackageUsageAndUninstall(model.id)
|
||||||
Connections
|
Connections
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2018 Ultimaker B.V.
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import QtQuick 2.2
|
import QtQuick 2.10
|
||||||
import QtQuick.Dialogs 1.1
|
import QtQuick.Dialogs 1.1
|
||||||
import QtQuick.Window 2.2
|
import QtQuick.Window 2.2
|
||||||
import QtQuick.Controls 1.4
|
import QtQuick.Controls 1.4
|
||||||
|
@ -32,6 +32,7 @@ UM.Dialog
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
text: licenseDialog.pluginName + catalog.i18nc("@label", "This plugin contains a license.\nYou need to accept this license to install this plugin.\nDo you agree with the terms below?")
|
text: licenseDialog.pluginName + catalog.i18nc("@label", "This plugin contains a license.\nYou need to accept this license to install this plugin.\nDo you agree with the terms below?")
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
TextArea
|
TextArea
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2018 Ultimaker B.V.
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import QtQuick 2.7
|
import QtQuick 2.10
|
||||||
import QtQuick.Controls 1.4
|
import QtQuick.Controls 1.4
|
||||||
import QtQuick.Controls.Styles 1.4
|
import QtQuick.Controls.Styles 1.4
|
||||||
|
|
||||||
|
@ -18,5 +18,6 @@ Rectangle
|
||||||
{
|
{
|
||||||
centerIn: parent
|
centerIn: parent
|
||||||
}
|
}
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import QtQuick 2.2
|
||||||
import QtQuick.Controls 1.4
|
import QtQuick.Controls 1.4
|
||||||
import QtQuick.Controls.Styles 1.4
|
import QtQuick.Controls.Styles 1.4
|
||||||
import UM 1.1 as UM
|
import UM 1.1 as UM
|
||||||
|
import Cura 1.0 as Cura
|
||||||
|
|
||||||
|
|
||||||
Item
|
Item
|
||||||
|
@ -18,16 +19,19 @@ Item
|
||||||
property var activeLabel: catalog.i18nc("@action:button", "Cancel")
|
property var activeLabel: catalog.i18nc("@action:button", "Cancel")
|
||||||
property var completeLabel: catalog.i18nc("@action:button", "Installed")
|
property var completeLabel: catalog.i18nc("@action:button", "Installed")
|
||||||
|
|
||||||
property var readyAction: null // Action when button is ready and clicked (likely install)
|
signal readyAction() // Action when button is ready and clicked (likely install)
|
||||||
property var activeAction: null // Action when button is active and clicked (likely cancel)
|
signal activeAction() // Action when button is active and clicked (likely cancel)
|
||||||
property var completeAction: null // Action when button is complete and clicked (likely go to installed)
|
signal completeAction() // Action when button is complete and clicked (likely go to installed)
|
||||||
|
|
||||||
width: UM.Theme.getSize("toolbox_action_button").width
|
width: UM.Theme.getSize("toolbox_action_button").width
|
||||||
height: UM.Theme.getSize("toolbox_action_button").height
|
height: UM.Theme.getSize("toolbox_action_button").height
|
||||||
|
|
||||||
Button
|
Cura.PrimaryButton
|
||||||
{
|
{
|
||||||
id: button
|
id: button
|
||||||
|
width: UM.Theme.getSize("toolbox_action_button").width
|
||||||
|
height: UM.Theme.getSize("toolbox_action_button").height
|
||||||
|
fixedWidthMode: true
|
||||||
text:
|
text:
|
||||||
{
|
{
|
||||||
if (complete)
|
if (complete)
|
||||||
|
@ -47,101 +51,15 @@ Item
|
||||||
{
|
{
|
||||||
if (complete)
|
if (complete)
|
||||||
{
|
{
|
||||||
return completeAction()
|
completeAction()
|
||||||
}
|
}
|
||||||
else if (active)
|
else if (active)
|
||||||
{
|
{
|
||||||
return activeAction()
|
activeAction()
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return readyAction()
|
readyAction()
|
||||||
}
|
|
||||||
}
|
|
||||||
style: ButtonStyle
|
|
||||||
{
|
|
||||||
background: Rectangle
|
|
||||||
{
|
|
||||||
implicitWidth: UM.Theme.getSize("toolbox_action_button").width
|
|
||||||
implicitHeight: UM.Theme.getSize("toolbox_action_button").height
|
|
||||||
color:
|
|
||||||
{
|
|
||||||
if (base.complete)
|
|
||||||
{
|
|
||||||
return "transparent"
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (control.hovered)
|
|
||||||
{
|
|
||||||
return UM.Theme.getColor("primary_hover")
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return UM.Theme.getColor("primary")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
border
|
|
||||||
{
|
|
||||||
width:
|
|
||||||
{
|
|
||||||
if (base.complete)
|
|
||||||
{
|
|
||||||
UM.Theme.getSize("default_lining").width
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
color:
|
|
||||||
{
|
|
||||||
if (control.hovered)
|
|
||||||
{
|
|
||||||
return UM.Theme.getColor("primary_hover")
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return UM.Theme.getColor("lining")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
label: Label
|
|
||||||
{
|
|
||||||
text: control.text
|
|
||||||
color:
|
|
||||||
{
|
|
||||||
if (base.complete)
|
|
||||||
{
|
|
||||||
return UM.Theme.getColor("text")
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (control.hovered)
|
|
||||||
{
|
|
||||||
return UM.Theme.getColor("button_text_hover")
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return UM.Theme.getColor("button_text")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
font:
|
|
||||||
{
|
|
||||||
if (base.complete)
|
|
||||||
{
|
|
||||||
return UM.Theme.getFont("default")
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return UM.Theme.getFont("default_bold")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,51 +1,51 @@
|
||||||
// Copyright (c) 2018 Ultimaker B.V.
|
// Copyright (c) 2018 Ultimaker B.V.
|
||||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import QtQuick 2.2
|
import QtQuick 2.10
|
||||||
import QtQuick.Controls 1.4
|
import QtQuick.Controls 2.3
|
||||||
import QtQuick.Controls.Styles 1.4
|
|
||||||
import UM 1.1 as UM
|
import UM 1.1 as UM
|
||||||
|
|
||||||
Button
|
Button
|
||||||
{
|
{
|
||||||
|
id: control
|
||||||
property bool active: false
|
property bool active: false
|
||||||
style: ButtonStyle
|
hoverEnabled: true
|
||||||
|
|
||||||
|
background: Item
|
||||||
{
|
{
|
||||||
background: Rectangle
|
implicitWidth: UM.Theme.getSize("toolbox_header_tab").width
|
||||||
|
implicitHeight: UM.Theme.getSize("toolbox_header_tab").height
|
||||||
|
Rectangle
|
||||||
{
|
{
|
||||||
color: "transparent"
|
visible: control.active
|
||||||
implicitWidth: UM.Theme.getSize("toolbox_header_tab").width
|
color: UM.Theme.getColor("primary")
|
||||||
implicitHeight: UM.Theme.getSize("toolbox_header_tab").height
|
anchors.bottom: parent.bottom
|
||||||
Rectangle
|
width: parent.width
|
||||||
{
|
height: UM.Theme.getSize("toolbox_header_highlight").height
|
||||||
visible: control.active
|
|
||||||
color: UM.Theme.getColor("toolbox_header_highlight_hover")
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
width: parent.width
|
|
||||||
height: UM.Theme.getSize("toolbox_header_highlight").height
|
|
||||||
}
|
|
||||||
}
|
|
||||||
label: Label
|
|
||||||
{
|
|
||||||
text: control.text
|
|
||||||
color:
|
|
||||||
{
|
|
||||||
if(control.hovered)
|
|
||||||
{
|
|
||||||
return UM.Theme.getColor("toolbox_header_button_text_hovered");
|
|
||||||
}
|
|
||||||
if(control.active)
|
|
||||||
{
|
|
||||||
return UM.Theme.getColor("toolbox_header_button_text_active");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return UM.Theme.getColor("toolbox_header_button_text_inactive");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
font: control.enabled ? (control.active ? UM.Theme.getFont("medium_bold") : UM.Theme.getFont("medium")) : UM.Theme.getFont("default_italic")
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
contentItem: Label
|
||||||
|
{
|
||||||
|
id: label
|
||||||
|
text: control.text
|
||||||
|
color:
|
||||||
|
{
|
||||||
|
if(control.hovered)
|
||||||
|
{
|
||||||
|
return UM.Theme.getColor("toolbox_header_button_text_hovered");
|
||||||
|
}
|
||||||
|
if(control.active)
|
||||||
|
{
|
||||||
|
return UM.Theme.getColor("toolbox_header_button_text_active");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return UM.Theme.getColor("toolbox_header_button_text_inactive");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
font: control.enabled ? (control.active ? UM.Theme.getFont("medium_bold") : UM.Theme.getFont("medium")) : UM.Theme.getFont("default_italic")
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
renderType: Text.NativeRendering
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -2,18 +2,19 @@
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from typing import Dict
|
from typing import Dict, List, Optional, Union
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, pyqtProperty, pyqtSignal
|
from PyQt5.QtCore import Qt, pyqtProperty, pyqtSignal
|
||||||
|
|
||||||
from UM.Qt.ListModel import ListModel
|
from UM.Qt.ListModel import ListModel
|
||||||
|
|
||||||
|
|
||||||
## Model that holds cura packages. By setting the filter property the instances held by this model can be changed.
|
## Model that holds cura packages. By setting the filter property the instances held by this model can be changed.
|
||||||
class AuthorsModel(ListModel):
|
class AuthorsModel(ListModel):
|
||||||
def __init__(self, parent = None):
|
def __init__(self, parent = None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
self._metadata = None
|
self._metadata = None # type: Optional[List[Dict[str, Union[str, List[str], int]]]]
|
||||||
|
|
||||||
self.addRoleName(Qt.UserRole + 1, "id")
|
self.addRoleName(Qt.UserRole + 1, "id")
|
||||||
self.addRoleName(Qt.UserRole + 2, "name")
|
self.addRoleName(Qt.UserRole + 2, "name")
|
||||||
|
@ -25,39 +26,40 @@ class AuthorsModel(ListModel):
|
||||||
self.addRoleName(Qt.UserRole + 8, "description")
|
self.addRoleName(Qt.UserRole + 8, "description")
|
||||||
|
|
||||||
# List of filters for queries. The result is the union of the each list of results.
|
# List of filters for queries. The result is the union of the each list of results.
|
||||||
self._filter = {} # type: Dict[str,str]
|
self._filter = {} # type: Dict[str, str]
|
||||||
|
|
||||||
def setMetadata(self, data):
|
def setMetadata(self, data: List[Dict[str, Union[str, List[str], int]]]):
|
||||||
self._metadata = data
|
if self._metadata != data:
|
||||||
self._update()
|
self._metadata = data
|
||||||
|
self._update()
|
||||||
|
|
||||||
def _update(self):
|
def _update(self) -> None:
|
||||||
items = []
|
items = [] # type: List[Dict[str, Union[str, List[str], int, None]]]
|
||||||
if not self._metadata:
|
if not self._metadata:
|
||||||
self.setItems([])
|
self.setItems(items)
|
||||||
return
|
return
|
||||||
|
|
||||||
for author in self._metadata:
|
for author in self._metadata:
|
||||||
items.append({
|
items.append({
|
||||||
"id": author["author_id"],
|
"id": author.get("author_id"),
|
||||||
"name": author["display_name"],
|
"name": author.get("display_name"),
|
||||||
"email": author["email"] if "email" in author else None,
|
"email": author.get("email"),
|
||||||
"website": author["website"],
|
"website": author.get("website"),
|
||||||
"package_count": author["package_count"] if "package_count" in author else 0,
|
"package_count": author.get("package_count", 0),
|
||||||
"package_types": author["package_types"] if "package_types" in author else [],
|
"package_types": author.get("package_types", []),
|
||||||
"icon_url": author["icon_url"] if "icon_url" in author else None,
|
"icon_url": author.get("icon_url"),
|
||||||
"description": "Material and quality profiles from {author_name}".format(author_name = author["display_name"])
|
"description": "Material and quality profiles from {author_name}".format(author_name = author.get("display_name", ""))
|
||||||
})
|
})
|
||||||
|
|
||||||
# Filter on all the key-word arguments.
|
# Filter on all the key-word arguments.
|
||||||
for key, value in self._filter.items():
|
for key, value in self._filter.items():
|
||||||
if key is "package_types":
|
if key is "package_types":
|
||||||
key_filter = lambda item, value = value: value in item["package_types"]
|
key_filter = lambda item, value = value: value in item["package_types"] # type: ignore
|
||||||
elif "*" in value:
|
elif "*" in value:
|
||||||
key_filter = lambda item, key = key, value = value: self._matchRegExp(item, key, value)
|
key_filter = lambda item, key = key, value = value: self._matchRegExp(item, key, value) # type: ignore
|
||||||
else:
|
else:
|
||||||
key_filter = lambda item, key = key, value = value: self._matchString(item, key, value)
|
key_filter = lambda item, key = key, value = value: self._matchString(item, key, value) # type: ignore
|
||||||
items = filter(key_filter, items)
|
items = filter(key_filter, items) # type: ignore
|
||||||
|
|
||||||
# Execute all filters.
|
# Execute all filters.
|
||||||
filtered_items = list(items)
|
filtered_items = list(items)
|
||||||
|
|
|
@ -33,20 +33,22 @@ class PackagesModel(ListModel):
|
||||||
self.addRoleName(Qt.UserRole + 12, "last_updated")
|
self.addRoleName(Qt.UserRole + 12, "last_updated")
|
||||||
self.addRoleName(Qt.UserRole + 13, "is_bundled")
|
self.addRoleName(Qt.UserRole + 13, "is_bundled")
|
||||||
self.addRoleName(Qt.UserRole + 14, "is_active")
|
self.addRoleName(Qt.UserRole + 14, "is_active")
|
||||||
self.addRoleName(Qt.UserRole + 15, "is_installed") # Scheduled pkgs are included in the model but should not be marked as actually installed
|
self.addRoleName(Qt.UserRole + 15, "is_installed") # Scheduled pkgs are included in the model but should not be marked as actually installed
|
||||||
self.addRoleName(Qt.UserRole + 16, "has_configs")
|
self.addRoleName(Qt.UserRole + 16, "has_configs")
|
||||||
self.addRoleName(Qt.UserRole + 17, "supported_configs")
|
self.addRoleName(Qt.UserRole + 17, "supported_configs")
|
||||||
self.addRoleName(Qt.UserRole + 18, "download_count")
|
self.addRoleName(Qt.UserRole + 18, "download_count")
|
||||||
self.addRoleName(Qt.UserRole + 19, "tags")
|
self.addRoleName(Qt.UserRole + 19, "tags")
|
||||||
self.addRoleName(Qt.UserRole + 20, "links")
|
self.addRoleName(Qt.UserRole + 20, "links")
|
||||||
self.addRoleName(Qt.UserRole + 21, "website")
|
self.addRoleName(Qt.UserRole + 21, "website")
|
||||||
|
self.addRoleName(Qt.UserRole + 22, "login_required")
|
||||||
|
|
||||||
# List of filters for queries. The result is the union of the each list of results.
|
# List of filters for queries. The result is the union of the each list of results.
|
||||||
self._filter = {} # type: Dict[str, str]
|
self._filter = {} # type: Dict[str, str]
|
||||||
|
|
||||||
def setMetadata(self, data):
|
def setMetadata(self, data):
|
||||||
self._metadata = data
|
if self._metadata != data:
|
||||||
self._update()
|
self._metadata = data
|
||||||
|
self._update()
|
||||||
|
|
||||||
def _update(self):
|
def _update(self):
|
||||||
items = []
|
items = []
|
||||||
|
@ -99,6 +101,7 @@ class PackagesModel(ListModel):
|
||||||
"tags": package["tags"] if "tags" in package else [],
|
"tags": package["tags"] if "tags" in package else [],
|
||||||
"links": links_dict,
|
"links": links_dict,
|
||||||
"website": package["website"] if "website" in package else None,
|
"website": package["website"] if "website" in package else None,
|
||||||
|
"login_required": "login-required" in package.get("tags", [])
|
||||||
})
|
})
|
||||||
|
|
||||||
# Filter on all the key-word arguments.
|
# Filter on all the key-word arguments.
|
||||||
|
|
|
@ -18,6 +18,7 @@ from UM.i18n import i18nCatalog
|
||||||
from UM.Version import Version
|
from UM.Version import Version
|
||||||
|
|
||||||
import cura
|
import cura
|
||||||
|
from cura import CuraConstants
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
|
|
||||||
from .AuthorsModel import AuthorsModel
|
from .AuthorsModel import AuthorsModel
|
||||||
|
@ -31,17 +32,17 @@ 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_ROOT = "https://api.ultimaker.com" # type: str
|
||||||
DEFAULT_CLOUD_API_VERSION = 1 #type: int
|
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 = CuraConstants.CuraSDKVersion # type: Union[str, int]
|
||||||
self._cloud_api_version = None # type: Optional[int]
|
self._cloud_api_version = CuraConstants.CuraCloudAPIVersion # type: int
|
||||||
self._cloud_api_root = None # type: Optional[str]
|
self._cloud_api_root = CuraConstants.CuraCloudAPIRoot # type: str
|
||||||
self._api_url = None # type: Optional[str]
|
self._api_url = None # type: Optional[str]
|
||||||
|
|
||||||
# Network:
|
# Network:
|
||||||
|
@ -66,31 +67,26 @@ class Toolbox(QObject, Extension):
|
||||||
self._old_plugin_ids = set() # type: Set[str]
|
self._old_plugin_ids = set() # type: Set[str]
|
||||||
self._old_plugin_metadata = dict() # type: Dict[str, Dict[str, Any]]
|
self._old_plugin_metadata = dict() # type: Dict[str, Dict[str, Any]]
|
||||||
|
|
||||||
# Data:
|
# The responses as given by the server parsed to a list.
|
||||||
self._metadata = {
|
self._server_response_data = {
|
||||||
"authors": [],
|
"authors": [],
|
||||||
"packages": [],
|
"packages": []
|
||||||
"plugins_showcase": [],
|
|
||||||
"plugins_available": [],
|
|
||||||
"plugins_installed": [],
|
|
||||||
"materials_showcase": [],
|
|
||||||
"materials_available": [],
|
|
||||||
"materials_installed": [],
|
|
||||||
"materials_generic": []
|
|
||||||
} # type: Dict[str, List[Any]]
|
} # type: Dict[str, List[Any]]
|
||||||
|
|
||||||
# Models:
|
# Models:
|
||||||
self._models = {
|
self._models = {
|
||||||
"authors": AuthorsModel(self),
|
"authors": AuthorsModel(self),
|
||||||
"packages": PackagesModel(self),
|
"packages": PackagesModel(self),
|
||||||
"plugins_showcase": PackagesModel(self),
|
} # type: Dict[str, Union[AuthorsModel, PackagesModel]]
|
||||||
"plugins_available": PackagesModel(self),
|
|
||||||
"plugins_installed": PackagesModel(self),
|
self._plugins_showcase_model = PackagesModel(self)
|
||||||
"materials_showcase": AuthorsModel(self),
|
self._plugins_available_model = PackagesModel(self)
|
||||||
"materials_available": AuthorsModel(self),
|
self._plugins_installed_model = PackagesModel(self)
|
||||||
"materials_installed": PackagesModel(self),
|
|
||||||
"materials_generic": PackagesModel(self)
|
self._materials_showcase_model = AuthorsModel(self)
|
||||||
} # type: Dict[str, ListModel]
|
self._materials_available_model = AuthorsModel(self)
|
||||||
|
self._materials_installed_model = PackagesModel(self)
|
||||||
|
self._materials_generic_model = PackagesModel(self)
|
||||||
|
|
||||||
# These properties are for keeping track of the UI state:
|
# These properties are for keeping track of the UI state:
|
||||||
# ----------------------------------------------------------------------
|
# ----------------------------------------------------------------------
|
||||||
|
@ -168,9 +164,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,
|
||||||
|
@ -178,44 +171,9 @@ class Toolbox(QObject, Extension):
|
||||||
)
|
)
|
||||||
self._request_urls = {
|
self._request_urls = {
|
||||||
"authors": QUrl("{base_url}/authors".format(base_url = self._api_url)),
|
"authors": QUrl("{base_url}/authors".format(base_url = self._api_url)),
|
||||||
"packages": QUrl("{base_url}/packages".format(base_url = self._api_url)),
|
"packages": QUrl("{base_url}/packages".format(base_url = self._api_url))
|
||||||
"plugins_showcase": QUrl("{base_url}/showcase".format(base_url = self._api_url)),
|
|
||||||
"plugins_available": QUrl("{base_url}/packages?package_type=plugin".format(base_url = self._api_url)),
|
|
||||||
"materials_showcase": QUrl("{base_url}/showcase".format(base_url = self._api_url)),
|
|
||||||
"materials_available": QUrl("{base_url}/packages?package_type=material".format(base_url = self._api_url)),
|
|
||||||
"materials_generic": QUrl("{base_url}/packages?package_type=material&tags=generic".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:
|
||||||
|
@ -231,12 +189,6 @@ class Toolbox(QObject, Extension):
|
||||||
# Make remote requests:
|
# Make remote requests:
|
||||||
self._makeRequestByType("packages")
|
self._makeRequestByType("packages")
|
||||||
self._makeRequestByType("authors")
|
self._makeRequestByType("authors")
|
||||||
# TODO: Uncomment in the future when the tag-filtered api calls work in the cloud server
|
|
||||||
# self._makeRequestByType("plugins_showcase")
|
|
||||||
# self._makeRequestByType("plugins_available")
|
|
||||||
# self._makeRequestByType("materials_showcase")
|
|
||||||
# self._makeRequestByType("materials_available")
|
|
||||||
# self._makeRequestByType("materials_generic")
|
|
||||||
|
|
||||||
# Gather installed packages:
|
# Gather installed packages:
|
||||||
self._updateInstalledModels()
|
self._updateInstalledModels()
|
||||||
|
@ -281,7 +233,7 @@ class Toolbox(QObject, Extension):
|
||||||
"description": plugin_data["plugin"]["description"]
|
"description": plugin_data["plugin"]["description"]
|
||||||
}
|
}
|
||||||
return formatted
|
return formatted
|
||||||
except:
|
except KeyError:
|
||||||
Logger.log("w", "Unable to convert plugin meta data %s", str(plugin_data))
|
Logger.log("w", "Unable to convert plugin meta data %s", str(plugin_data))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -319,13 +271,10 @@ class Toolbox(QObject, Extension):
|
||||||
if plugin_id not in all_plugin_package_ids)
|
if plugin_id not in all_plugin_package_ids)
|
||||||
self._old_plugin_metadata = {k: v for k, v in self._old_plugin_metadata.items() if k in self._old_plugin_ids}
|
self._old_plugin_metadata = {k: v for k, v in self._old_plugin_metadata.items() if k in self._old_plugin_ids}
|
||||||
|
|
||||||
self._metadata["plugins_installed"] = all_packages["plugin"] + list(self._old_plugin_metadata.values())
|
self._plugins_installed_model.setMetadata(all_packages["plugin"] + list(self._old_plugin_metadata.values()))
|
||||||
self._models["plugins_installed"].setMetadata(self._metadata["plugins_installed"])
|
|
||||||
self.metadataChanged.emit()
|
self.metadataChanged.emit()
|
||||||
if "material" in all_packages:
|
if "material" in all_packages:
|
||||||
self._metadata["materials_installed"] = all_packages["material"]
|
self._materials_installed_model.setMetadata(all_packages["material"])
|
||||||
# TODO: ADD MATERIALS HERE ONCE MATERIALS PORTION OF TOOLBOX IS LIVE
|
|
||||||
self._models["materials_installed"].setMetadata(self._metadata["materials_installed"])
|
|
||||||
self.metadataChanged.emit()
|
self.metadataChanged.emit()
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
|
@ -479,7 +428,7 @@ class Toolbox(QObject, Extension):
|
||||||
def getRemotePackage(self, package_id: str) -> Optional[Dict]:
|
def getRemotePackage(self, package_id: str) -> Optional[Dict]:
|
||||||
# TODO: make the lookup in a dict, not a loop. canUpdate is called for every item.
|
# TODO: make the lookup in a dict, not a loop. canUpdate is called for every item.
|
||||||
remote_package = None
|
remote_package = None
|
||||||
for package in self._metadata["packages"]:
|
for package in self._server_response_data["packages"]:
|
||||||
if package["package_id"] == package_id:
|
if package["package_id"] == package_id:
|
||||||
remote_package = package
|
remote_package = package
|
||||||
break
|
break
|
||||||
|
@ -491,11 +440,8 @@ class Toolbox(QObject, Extension):
|
||||||
def canUpdate(self, package_id: str) -> bool:
|
def canUpdate(self, package_id: str) -> bool:
|
||||||
local_package = self._package_manager.getInstalledPackageInfo(package_id)
|
local_package = self._package_manager.getInstalledPackageInfo(package_id)
|
||||||
if local_package is None:
|
if local_package is None:
|
||||||
Logger.log("i", "Could not find package [%s] as installed in the package manager, fall back to check the old plugins",
|
|
||||||
package_id)
|
|
||||||
local_package = self.getOldPluginPackageMetadata(package_id)
|
local_package = self.getOldPluginPackageMetadata(package_id)
|
||||||
if local_package is None:
|
if local_package is None:
|
||||||
Logger.log("i", "Could not find package [%s] in the old plugins", package_id)
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
remote_package = self.getRemotePackage(package_id)
|
remote_package = self.getRemotePackage(package_id)
|
||||||
|
@ -545,8 +491,8 @@ class Toolbox(QObject, Extension):
|
||||||
@pyqtSlot(str, result = int)
|
@pyqtSlot(str, result = int)
|
||||||
def getNumberOfInstalledPackagesByAuthor(self, author_id: str) -> int:
|
def getNumberOfInstalledPackagesByAuthor(self, author_id: str) -> int:
|
||||||
count = 0
|
count = 0
|
||||||
for package in self._metadata["materials_installed"]:
|
for package in self._materials_installed_model.items:
|
||||||
if package["author"]["author_id"] == author_id:
|
if package["author_id"] == author_id:
|
||||||
count += 1
|
count += 1
|
||||||
return count
|
return count
|
||||||
|
|
||||||
|
@ -554,7 +500,7 @@ class Toolbox(QObject, Extension):
|
||||||
@pyqtSlot(str, result = int)
|
@pyqtSlot(str, result = int)
|
||||||
def getTotalNumberOfMaterialPackagesByAuthor(self, author_id: str) -> int:
|
def getTotalNumberOfMaterialPackagesByAuthor(self, author_id: str) -> int:
|
||||||
count = 0
|
count = 0
|
||||||
for package in self._metadata["packages"]:
|
for package in self._server_response_data["packages"]:
|
||||||
if package["package_type"] == "material":
|
if package["package_type"] == "material":
|
||||||
if package["author"]["author_id"] == author_id:
|
if package["author"]["author_id"] == author_id:
|
||||||
count += 1
|
count += 1
|
||||||
|
@ -568,34 +514,30 @@ class Toolbox(QObject, Extension):
|
||||||
|
|
||||||
# Check for plugins that were installed with the old plugin browser
|
# Check for plugins that were installed with the old plugin browser
|
||||||
def isOldPlugin(self, plugin_id: str) -> bool:
|
def isOldPlugin(self, plugin_id: str) -> bool:
|
||||||
if plugin_id in self._old_plugin_ids:
|
return plugin_id in self._old_plugin_ids
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def getOldPluginPackageMetadata(self, plugin_id: str) -> Optional[Dict[str, Any]]:
|
def getOldPluginPackageMetadata(self, plugin_id: str) -> Optional[Dict[str, Any]]:
|
||||||
return self._old_plugin_metadata.get(plugin_id)
|
return self._old_plugin_metadata.get(plugin_id)
|
||||||
|
|
||||||
def loadingComplete(self) -> bool:
|
def isLoadingComplete(self) -> bool:
|
||||||
populated = 0
|
populated = 0
|
||||||
for list in self._metadata.items():
|
for metadata_list in self._server_response_data.items():
|
||||||
if len(list) > 0:
|
if metadata_list:
|
||||||
populated += 1
|
populated += 1
|
||||||
if populated == len(self._metadata.items()):
|
return populated == len(self._server_response_data.items())
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Make API Calls
|
# Make API Calls
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
def _makeRequestByType(self, type: str) -> None:
|
def _makeRequestByType(self, request_type: str) -> None:
|
||||||
Logger.log("i", "Marketplace: Requesting %s metadata from server.", type)
|
Logger.log("i", "Requesting %s metadata from server.", request_type)
|
||||||
request = QNetworkRequest(self._request_urls[type])
|
request = QNetworkRequest(self._request_urls[request_type])
|
||||||
request.setRawHeader(*self._request_header)
|
request.setRawHeader(*self._request_header)
|
||||||
if self._network_manager:
|
if self._network_manager:
|
||||||
self._network_manager.get(request)
|
self._network_manager.get(request)
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def startDownload(self, url: str) -> None:
|
def startDownload(self, url: str) -> None:
|
||||||
Logger.log("i", "Marketplace: Attempting to download & install package from %s.", url)
|
Logger.log("i", "Attempting to download & install package from %s.", url)
|
||||||
url = QUrl(url)
|
url = QUrl(url)
|
||||||
self._download_request = QNetworkRequest(url)
|
self._download_request = QNetworkRequest(url)
|
||||||
if hasattr(QNetworkRequest, "FollowRedirectsAttribute"):
|
if hasattr(QNetworkRequest, "FollowRedirectsAttribute"):
|
||||||
|
@ -612,15 +554,15 @@ class Toolbox(QObject, Extension):
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def cancelDownload(self) -> None:
|
def cancelDownload(self) -> None:
|
||||||
Logger.log("i", "Marketplace: User cancelled the download of a package.")
|
Logger.log("i", "User cancelled the download of a package.")
|
||||||
self.resetDownload()
|
self.resetDownload()
|
||||||
|
|
||||||
def resetDownload(self) -> None:
|
def resetDownload(self) -> None:
|
||||||
if self._download_reply:
|
if self._download_reply:
|
||||||
try:
|
try:
|
||||||
self._download_reply.downloadProgress.disconnect(self._onDownloadProgress)
|
self._download_reply.downloadProgress.disconnect(self._onDownloadProgress)
|
||||||
except TypeError: #Raised when the method is not connected to the signal yet.
|
except TypeError: # Raised when the method is not connected to the signal yet.
|
||||||
pass #Don't need to disconnect.
|
pass # Don't need to disconnect.
|
||||||
self._download_reply.abort()
|
self._download_reply.abort()
|
||||||
self._download_reply = None
|
self._download_reply = None
|
||||||
self._download_request = None
|
self._download_request = None
|
||||||
|
@ -646,22 +588,8 @@ class Toolbox(QObject, Extension):
|
||||||
self.resetDownload()
|
self.resetDownload()
|
||||||
return
|
return
|
||||||
|
|
||||||
# HACK: These request are not handled independently at this moment, but together from the "packages" call
|
|
||||||
do_not_handle = [
|
|
||||||
"materials_available",
|
|
||||||
"materials_showcase",
|
|
||||||
"materials_generic",
|
|
||||||
"plugins_available",
|
|
||||||
"plugins_showcase",
|
|
||||||
]
|
|
||||||
|
|
||||||
if reply.operation() == QNetworkAccessManager.GetOperation:
|
if reply.operation() == QNetworkAccessManager.GetOperation:
|
||||||
for type, url in self._request_urls.items():
|
for response_type, url in self._request_urls.items():
|
||||||
|
|
||||||
# HACK: Do nothing because we'll handle these from the "packages" call
|
|
||||||
if type in do_not_handle:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if reply.url() == url:
|
if reply.url() == url:
|
||||||
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200:
|
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200:
|
||||||
try:
|
try:
|
||||||
|
@ -674,38 +602,32 @@ class Toolbox(QObject, Extension):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Create model and apply metadata:
|
# Create model and apply metadata:
|
||||||
if not self._models[type]:
|
if not self._models[response_type]:
|
||||||
Logger.log("e", "Could not find the %s model.", type)
|
Logger.log("e", "Could not find the %s model.", response_type)
|
||||||
break
|
break
|
||||||
|
|
||||||
self._metadata[type] = json_data["data"]
|
self._server_response_data[response_type] = json_data["data"]
|
||||||
self._models[type].setMetadata(self._metadata[type])
|
self._models[response_type].setMetadata(self._server_response_data[response_type])
|
||||||
|
|
||||||
# Do some auto filtering
|
if response_type is "packages":
|
||||||
# TODO: Make multiple API calls in the future to handle this
|
self._models[response_type].setFilter({"type": "plugin"})
|
||||||
if type is "packages":
|
self.reBuildMaterialsModels()
|
||||||
self._models[type].setFilter({"type": "plugin"})
|
self.reBuildPluginsModels()
|
||||||
self.buildMaterialsModels()
|
elif response_type is "authors":
|
||||||
self.buildPluginsModels()
|
self._models[response_type].setFilter({"package_types": "material"})
|
||||||
if type is "authors":
|
self._models[response_type].setFilter({"tags": "generic"})
|
||||||
self._models[type].setFilter({"package_types": "material"})
|
|
||||||
if type is "materials_generic":
|
|
||||||
self._models[type].setFilter({"tags": "generic"})
|
|
||||||
|
|
||||||
self.metadataChanged.emit()
|
self.metadataChanged.emit()
|
||||||
|
|
||||||
if self.loadingComplete() is True:
|
if self.isLoadingComplete():
|
||||||
self.setViewPage("overview")
|
self.setViewPage("overview")
|
||||||
|
|
||||||
return
|
|
||||||
except json.decoder.JSONDecodeError:
|
except json.decoder.JSONDecodeError:
|
||||||
Logger.log("w", "Marketplace: Received invalid JSON for %s.", type)
|
Logger.log("w", "Received invalid JSON for %s.", response_type)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
self.setViewPage("errored")
|
self.setViewPage("errored")
|
||||||
self.resetDownload()
|
self.resetDownload()
|
||||||
return
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Ignore any operation that is not a get operation
|
# Ignore any operation that is not a get operation
|
||||||
pass
|
pass
|
||||||
|
@ -716,7 +638,13 @@ class Toolbox(QObject, Extension):
|
||||||
self.setDownloadProgress(new_progress)
|
self.setDownloadProgress(new_progress)
|
||||||
if bytes_sent == bytes_total:
|
if bytes_sent == bytes_total:
|
||||||
self.setIsDownloading(False)
|
self.setIsDownloading(False)
|
||||||
cast(QNetworkReply, self._download_reply).downloadProgress.disconnect(self._onDownloadProgress)
|
self._download_reply = cast(QNetworkReply, self._download_reply)
|
||||||
|
self._download_reply.downloadProgress.disconnect(self._onDownloadProgress)
|
||||||
|
|
||||||
|
# Check if the download was sucessfull
|
||||||
|
if self._download_reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
|
||||||
|
Logger.log("w", "Failed to download package. The following error was returned: %s", json.loads(bytes(self._download_reply.readAll()).decode("utf-8")))
|
||||||
|
return
|
||||||
# Must not delete the temporary file on Windows
|
# Must not delete the temporary file on Windows
|
||||||
self._temp_plugin_file = tempfile.NamedTemporaryFile(mode = "w+b", suffix = ".curapackage", delete = False)
|
self._temp_plugin_file = tempfile.NamedTemporaryFile(mode = "w+b", suffix = ".curapackage", delete = False)
|
||||||
file_path = self._temp_plugin_file.name
|
file_path = self._temp_plugin_file.name
|
||||||
|
@ -726,10 +654,10 @@ class Toolbox(QObject, Extension):
|
||||||
self._onDownloadComplete(file_path)
|
self._onDownloadComplete(file_path)
|
||||||
|
|
||||||
def _onDownloadComplete(self, file_path: str) -> None:
|
def _onDownloadComplete(self, file_path: str) -> None:
|
||||||
Logger.log("i", "Marketplace: Download complete.")
|
Logger.log("i", "Download complete.")
|
||||||
package_info = self._package_manager.getPackageInfo(file_path)
|
package_info = self._package_manager.getPackageInfo(file_path)
|
||||||
if not package_info:
|
if not package_info:
|
||||||
Logger.log("w", "Marketplace: Package file [%s] was not a valid CuraPackage.", file_path)
|
Logger.log("w", "Package file [%s] was not a valid CuraPackage.", file_path)
|
||||||
return
|
return
|
||||||
|
|
||||||
license_content = self._package_manager.getPackageLicense(file_path)
|
license_content = self._package_manager.getPackageLicense(file_path)
|
||||||
|
@ -738,7 +666,6 @@ class Toolbox(QObject, Extension):
|
||||||
return
|
return
|
||||||
|
|
||||||
self.install(file_path)
|
self.install(file_path)
|
||||||
return
|
|
||||||
|
|
||||||
# Getter & Setters for Properties:
|
# Getter & Setters for Properties:
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
|
@ -761,8 +688,9 @@ class Toolbox(QObject, Extension):
|
||||||
return self._is_downloading
|
return self._is_downloading
|
||||||
|
|
||||||
def setActivePackage(self, package: Dict[str, Any]) -> None:
|
def setActivePackage(self, package: Dict[str, Any]) -> None:
|
||||||
self._active_package = package
|
if self._active_package != package:
|
||||||
self.activePackageChanged.emit()
|
self._active_package = package
|
||||||
|
self.activePackageChanged.emit()
|
||||||
|
|
||||||
## The active package is the package that is currently being downloaded
|
## The active package is the package that is currently being downloaded
|
||||||
@pyqtProperty(QObject, fset = setActivePackage, notify = activePackageChanged)
|
@pyqtProperty(QObject, fset = setActivePackage, notify = activePackageChanged)
|
||||||
|
@ -770,16 +698,18 @@ class Toolbox(QObject, Extension):
|
||||||
return self._active_package
|
return self._active_package
|
||||||
|
|
||||||
def setViewCategory(self, category: str = "plugin") -> None:
|
def setViewCategory(self, category: str = "plugin") -> None:
|
||||||
self._view_category = category
|
if self._view_category != category:
|
||||||
self.viewChanged.emit()
|
self._view_category = category
|
||||||
|
self.viewChanged.emit()
|
||||||
|
|
||||||
@pyqtProperty(str, fset = setViewCategory, notify = viewChanged)
|
@pyqtProperty(str, fset = setViewCategory, notify = viewChanged)
|
||||||
def viewCategory(self) -> str:
|
def viewCategory(self) -> str:
|
||||||
return self._view_category
|
return self._view_category
|
||||||
|
|
||||||
def setViewPage(self, page: str = "overview") -> None:
|
def setViewPage(self, page: str = "overview") -> None:
|
||||||
self._view_page = page
|
if self._view_page != page:
|
||||||
self.viewChanged.emit()
|
self._view_page = page
|
||||||
|
self.viewChanged.emit()
|
||||||
|
|
||||||
@pyqtProperty(str, fset = setViewPage, notify = viewChanged)
|
@pyqtProperty(str, fset = setViewPage, notify = viewChanged)
|
||||||
def viewPage(self) -> str:
|
def viewPage(self) -> str:
|
||||||
|
@ -787,48 +717,48 @@ class Toolbox(QObject, Extension):
|
||||||
|
|
||||||
# Exposed Models:
|
# Exposed Models:
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
@pyqtProperty(QObject, notify = metadataChanged)
|
@pyqtProperty(QObject, constant=True)
|
||||||
def authorsModel(self) -> AuthorsModel:
|
def authorsModel(self) -> AuthorsModel:
|
||||||
return cast(AuthorsModel, self._models["authors"])
|
return cast(AuthorsModel, self._models["authors"])
|
||||||
|
|
||||||
@pyqtProperty(QObject, notify = metadataChanged)
|
@pyqtProperty(QObject, constant=True)
|
||||||
def packagesModel(self) -> PackagesModel:
|
def packagesModel(self) -> PackagesModel:
|
||||||
return cast(PackagesModel, self._models["packages"])
|
return cast(PackagesModel, self._models["packages"])
|
||||||
|
|
||||||
@pyqtProperty(QObject, notify = metadataChanged)
|
@pyqtProperty(QObject, constant=True)
|
||||||
def pluginsShowcaseModel(self) -> PackagesModel:
|
def pluginsShowcaseModel(self) -> PackagesModel:
|
||||||
return cast(PackagesModel, self._models["plugins_showcase"])
|
return self._plugins_showcase_model
|
||||||
|
|
||||||
@pyqtProperty(QObject, notify = metadataChanged)
|
@pyqtProperty(QObject, constant=True)
|
||||||
def pluginsAvailableModel(self) -> PackagesModel:
|
def pluginsAvailableModel(self) -> PackagesModel:
|
||||||
return cast(PackagesModel, self._models["plugins_available"])
|
return self._plugins_available_model
|
||||||
|
|
||||||
@pyqtProperty(QObject, notify = metadataChanged)
|
@pyqtProperty(QObject, constant=True)
|
||||||
def pluginsInstalledModel(self) -> PackagesModel:
|
def pluginsInstalledModel(self) -> PackagesModel:
|
||||||
return cast(PackagesModel, self._models["plugins_installed"])
|
return self._plugins_installed_model
|
||||||
|
|
||||||
@pyqtProperty(QObject, notify = metadataChanged)
|
@pyqtProperty(QObject, constant=True)
|
||||||
def materialsShowcaseModel(self) -> AuthorsModel:
|
def materialsShowcaseModel(self) -> AuthorsModel:
|
||||||
return cast(AuthorsModel, self._models["materials_showcase"])
|
return self._materials_showcase_model
|
||||||
|
|
||||||
@pyqtProperty(QObject, notify = metadataChanged)
|
@pyqtProperty(QObject, constant=True)
|
||||||
def materialsAvailableModel(self) -> AuthorsModel:
|
def materialsAvailableModel(self) -> AuthorsModel:
|
||||||
return cast(AuthorsModel, self._models["materials_available"])
|
return self._materials_available_model
|
||||||
|
|
||||||
@pyqtProperty(QObject, notify = metadataChanged)
|
@pyqtProperty(QObject, constant=True)
|
||||||
def materialsInstalledModel(self) -> PackagesModel:
|
def materialsInstalledModel(self) -> PackagesModel:
|
||||||
return cast(PackagesModel, self._models["materials_installed"])
|
return self._materials_installed_model
|
||||||
|
|
||||||
@pyqtProperty(QObject, notify=metadataChanged)
|
@pyqtProperty(QObject, constant=True)
|
||||||
def materialsGenericModel(self) -> PackagesModel:
|
def materialsGenericModel(self) -> PackagesModel:
|
||||||
return cast(PackagesModel, self._models["materials_generic"])
|
return self._materials_generic_model
|
||||||
|
|
||||||
# Filter Models:
|
# Filter Models:
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
@pyqtSlot(str, str, str)
|
@pyqtSlot(str, str, str)
|
||||||
def filterModelByProp(self, model_type: str, filter_type: str, parameter: str) -> None:
|
def filterModelByProp(self, model_type: str, filter_type: str, parameter: str) -> None:
|
||||||
if not self._models[model_type]:
|
if not self._models[model_type]:
|
||||||
Logger.log("w", "Marketplace: Couldn't filter %s model because it doesn't exist.", model_type)
|
Logger.log("w", "Couldn't filter %s model because it doesn't exist.", model_type)
|
||||||
return
|
return
|
||||||
self._models[model_type].setFilter({filter_type: parameter})
|
self._models[model_type].setFilter({filter_type: parameter})
|
||||||
self.filterChanged.emit()
|
self.filterChanged.emit()
|
||||||
|
@ -836,7 +766,7 @@ class Toolbox(QObject, Extension):
|
||||||
@pyqtSlot(str, "QVariantMap")
|
@pyqtSlot(str, "QVariantMap")
|
||||||
def setFilters(self, model_type: str, filter_dict: dict) -> None:
|
def setFilters(self, model_type: str, filter_dict: dict) -> None:
|
||||||
if not self._models[model_type]:
|
if not self._models[model_type]:
|
||||||
Logger.log("w", "Marketplace: Couldn't filter %s model because it doesn't exist.", model_type)
|
Logger.log("w", "Couldn't filter %s model because it doesn't exist.", model_type)
|
||||||
return
|
return
|
||||||
self._models[model_type].setFilter(filter_dict)
|
self._models[model_type].setFilter(filter_dict)
|
||||||
self.filterChanged.emit()
|
self.filterChanged.emit()
|
||||||
|
@ -844,21 +774,21 @@ class Toolbox(QObject, Extension):
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def removeFilters(self, model_type: str) -> None:
|
def removeFilters(self, model_type: str) -> None:
|
||||||
if not self._models[model_type]:
|
if not self._models[model_type]:
|
||||||
Logger.log("w", "Marketplace: Couldn't remove filters on %s model because it doesn't exist.", model_type)
|
Logger.log("w", "Couldn't remove filters on %s model because it doesn't exist.", model_type)
|
||||||
return
|
return
|
||||||
self._models[model_type].setFilter({})
|
self._models[model_type].setFilter({})
|
||||||
self.filterChanged.emit()
|
self.filterChanged.emit()
|
||||||
|
|
||||||
# HACK(S):
|
# HACK(S):
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
def buildMaterialsModels(self) -> None:
|
def reBuildMaterialsModels(self) -> None:
|
||||||
self._metadata["materials_showcase"] = []
|
materials_showcase_metadata = []
|
||||||
self._metadata["materials_available"] = []
|
materials_available_metadata = []
|
||||||
self._metadata["materials_generic"] = []
|
materials_generic_metadata = []
|
||||||
|
|
||||||
processed_authors = [] # type: List[str]
|
processed_authors = [] # type: List[str]
|
||||||
|
|
||||||
for item in self._metadata["packages"]:
|
for item in self._server_response_data["packages"]:
|
||||||
if item["package_type"] == "material":
|
if item["package_type"] == "material":
|
||||||
|
|
||||||
author = item["author"]
|
author = item["author"]
|
||||||
|
@ -867,30 +797,29 @@ class Toolbox(QObject, Extension):
|
||||||
|
|
||||||
# Generic materials to be in the same section
|
# Generic materials to be in the same section
|
||||||
if "generic" in item["tags"]:
|
if "generic" in item["tags"]:
|
||||||
self._metadata["materials_generic"].append(item)
|
materials_generic_metadata.append(item)
|
||||||
else:
|
else:
|
||||||
if "showcase" in item["tags"]:
|
if "showcase" in item["tags"]:
|
||||||
self._metadata["materials_showcase"].append(author)
|
materials_showcase_metadata.append(author)
|
||||||
else:
|
else:
|
||||||
self._metadata["materials_available"].append(author)
|
materials_available_metadata.append(author)
|
||||||
|
|
||||||
processed_authors.append(author["author_id"])
|
processed_authors.append(author["author_id"])
|
||||||
|
|
||||||
self._models["materials_showcase"].setMetadata(self._metadata["materials_showcase"])
|
self._materials_showcase_model.setMetadata(materials_showcase_metadata)
|
||||||
self._models["materials_available"].setMetadata(self._metadata["materials_available"])
|
self._materials_available_model.setMetadata(materials_available_metadata)
|
||||||
self._models["materials_generic"].setMetadata(self._metadata["materials_generic"])
|
self._materials_generic_model.setMetadata(materials_generic_metadata)
|
||||||
|
|
||||||
def buildPluginsModels(self) -> None:
|
def reBuildPluginsModels(self) -> None:
|
||||||
self._metadata["plugins_showcase"] = []
|
plugins_showcase_metadata = []
|
||||||
self._metadata["plugins_available"] = []
|
plugins_available_metadata = []
|
||||||
|
|
||||||
for item in self._metadata["packages"]:
|
for item in self._server_response_data["packages"]:
|
||||||
if item["package_type"] == "plugin":
|
if item["package_type"] == "plugin":
|
||||||
|
|
||||||
if "showcase" in item["tags"]:
|
if "showcase" in item["tags"]:
|
||||||
self._metadata["plugins_showcase"].append(item)
|
plugins_showcase_metadata.append(item)
|
||||||
else:
|
else:
|
||||||
self._metadata["plugins_available"].append(item)
|
plugins_available_metadata.append(item)
|
||||||
|
|
||||||
self._models["plugins_showcase"].setMetadata(self._metadata["plugins_showcase"])
|
self._plugins_showcase_model.setMetadata(plugins_showcase_metadata)
|
||||||
self._models["plugins_available"].setMetadata(self._metadata["plugins_available"])
|
self._plugins_available_model.setMetadata(plugins_available_metadata)
|
||||||
|
|
|
@ -97,6 +97,7 @@ Item
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
visible: printJob
|
visible: printJob
|
||||||
|
width: 120 * screenScaleFactor // TODO: Theme!
|
||||||
|
|
||||||
// FIXED-LINE-HEIGHT:
|
// FIXED-LINE-HEIGHT:
|
||||||
height: 18 * screenScaleFactor // TODO: Theme!
|
height: 18 * screenScaleFactor // TODO: Theme!
|
||||||
|
|
|
@ -12,7 +12,19 @@ import UM 1.2 as UM
|
||||||
Item
|
Item
|
||||||
{
|
{
|
||||||
// The printer name
|
// The printer name
|
||||||
property alias text: printerNameLabel.text;
|
property var text: ""
|
||||||
|
property var tagText: {
|
||||||
|
switch(text) {
|
||||||
|
case "Ultimaker 3":
|
||||||
|
return "UM 3"
|
||||||
|
case "Ultimaker 3 Extended":
|
||||||
|
return "UM 3 EXT"
|
||||||
|
case "Ultimaker S5":
|
||||||
|
return "UM S5"
|
||||||
|
default:
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
implicitHeight: 18 * screenScaleFactor // TODO: Theme!
|
implicitHeight: 18 * screenScaleFactor // TODO: Theme!
|
||||||
implicitWidth: printerNameLabel.contentWidth + 12 // TODO: Theme!
|
implicitWidth: printerNameLabel.contentWidth + 12 // TODO: Theme!
|
||||||
|
@ -28,7 +40,7 @@ Item
|
||||||
id: printerNameLabel
|
id: printerNameLabel
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
color: "#535369" // TODO: Theme!
|
color: "#535369" // TODO: Theme!
|
||||||
text: ""
|
text: tagText
|
||||||
font.pointSize: 10
|
font.pointSize: 10
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -8,6 +8,7 @@ from PyQt5.QtCore import QUrl
|
||||||
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager
|
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager
|
||||||
|
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
from cura import CuraConstants
|
||||||
from cura.API import Account
|
from cura.API import Account
|
||||||
from .MeshUploader import MeshUploader
|
from .MeshUploader import MeshUploader
|
||||||
from ..Models import BaseModel
|
from ..Models import BaseModel
|
||||||
|
@ -24,8 +25,7 @@ from .Models.CloudPrintJobResponse import CloudPrintJobResponse
|
||||||
class CloudApiClient:
|
class CloudApiClient:
|
||||||
|
|
||||||
# The cloud URL to use for this remote cluster.
|
# The cloud URL to use for this remote cluster.
|
||||||
# TODO: Make sure that this URL goes to the live api before release
|
ROOT_PATH = CuraConstants.CuraCloudAPIRoot
|
||||||
ROOT_PATH = "https://api-staging.ultimaker.com"
|
|
||||||
CLUSTER_API_ROOT = "{}/connect/v1".format(ROOT_PATH)
|
CLUSTER_API_ROOT = "{}/connect/v1".format(ROOT_PATH)
|
||||||
CURA_API_ROOT = "{}/cura/v1".format(ROOT_PATH)
|
CURA_API_ROOT = "{}/cura/v1".format(ROOT_PATH)
|
||||||
|
|
||||||
|
|
|
@ -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": 5,
|
||||||
|
"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",
|
||||||
|
|
|
@ -3385,7 +3385,7 @@
|
||||||
"retraction_combing":
|
"retraction_combing":
|
||||||
{
|
{
|
||||||
"label": "Combing Mode",
|
"label": "Combing Mode",
|
||||||
"description": "Combing keeps the nozzle within already printed areas when traveling. This results in slightly longer travel moves but reduces the need for retractions. If combing is off, the material will retract and the nozzle moves in a straight line to the next point. It is also possible to avoid combing over top/bottom skin areas and also to only comb within the infill. Note that the 'Within Infill' option behaves exactly like the 'Not in Skin' option in earlier Cura releases.",
|
"description": "Combing keeps the nozzle within already printed areas when traveling. This results in slightly longer travel moves but reduces the need for retractions. If combing is off, the material will retract and the nozzle moves in a straight line to the next point. It is also possible to avoid combing over top/bottom skin areas or to only comb within the infill.",
|
||||||
"type": "enum",
|
"type": "enum",
|
||||||
"options":
|
"options":
|
||||||
{
|
{
|
||||||
|
|
|
@ -12,6 +12,12 @@ Item
|
||||||
{
|
{
|
||||||
id: widget
|
id: widget
|
||||||
|
|
||||||
|
function requestWriteToDevice()
|
||||||
|
{
|
||||||
|
UM.OutputDeviceManager.requestWriteToDevice(UM.OutputDeviceManager.activeDevice, PrintInformation.jobName,
|
||||||
|
{ "filter_by_machine": true, "preferred_mimetypes": Cura.MachineManager.activeMachine.preferred_output_file_formats });
|
||||||
|
}
|
||||||
|
|
||||||
Cura.PrimaryButton
|
Cura.PrimaryButton
|
||||||
{
|
{
|
||||||
id: saveToButton
|
id: saveToButton
|
||||||
|
@ -32,9 +38,8 @@ Item
|
||||||
|
|
||||||
onClicked:
|
onClicked:
|
||||||
{
|
{
|
||||||
forceActiveFocus();
|
forceActiveFocus()
|
||||||
UM.OutputDeviceManager.requestWriteToDevice(UM.OutputDeviceManager.activeDevice, PrintInformation.jobName,
|
widget.requestWriteToDevice()
|
||||||
{ "filter_by_machine": true, "preferred_mimetypes": Cura.MachineManager.activeMachine.preferred_output_file_formats });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,6 +86,7 @@ Item
|
||||||
delegate: Cura.ActionButton
|
delegate: Cura.ActionButton
|
||||||
{
|
{
|
||||||
text: model.description
|
text: model.description
|
||||||
|
visible: model.id != UM.OutputDeviceManager.activeDevice // Don't show the active device in the list
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
cornerRadius: 0
|
cornerRadius: 0
|
||||||
hoverColor: UM.Theme.getColor("primary")
|
hoverColor: UM.Theme.getColor("primary")
|
||||||
|
@ -88,6 +94,7 @@ Item
|
||||||
onClicked:
|
onClicked:
|
||||||
{
|
{
|
||||||
UM.OutputDeviceManager.setActiveDevice(model.id)
|
UM.OutputDeviceManager.setActiveDevice(model.id)
|
||||||
|
widget.requestWriteToDevice()
|
||||||
popup.close()
|
popup.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ UM.RecolorImage
|
||||||
width: UM.Theme.getSize("section_icon").width
|
width: UM.Theme.getSize("section_icon").width
|
||||||
height: UM.Theme.getSize("section_icon").height
|
height: UM.Theme.getSize("section_icon").height
|
||||||
|
|
||||||
color: popup.opened ? UM.Theme.getColor("primary") : UM.Theme.getColor("text_medium")
|
color: UM.Theme.getColor("icon")
|
||||||
|
|
||||||
MouseArea
|
MouseArea
|
||||||
{
|
{
|
||||||
|
|
|
@ -278,16 +278,33 @@ UM.MainWindow
|
||||||
height: UM.Theme.getSize("stage_menu").height
|
height: UM.Theme.getSize("stage_menu").height
|
||||||
source: UM.Controller.activeStage != null ? UM.Controller.activeStage.stageMenuComponent : ""
|
source: UM.Controller.activeStage != null ? UM.Controller.activeStage.stageMenuComponent : ""
|
||||||
|
|
||||||
|
// HACK: This is to ensure that the parent never gets set to null, as this wreaks havoc on the focus.
|
||||||
|
function onParentDestroyed()
|
||||||
|
{
|
||||||
|
printSetupSelector.parent = stageMenu
|
||||||
|
printSetupSelector.visible = false
|
||||||
|
}
|
||||||
|
property Item oldParent: null
|
||||||
|
|
||||||
// The printSetupSelector is defined here so that the setting list doesn't need to get re-instantiated
|
// The printSetupSelector is defined here so that the setting list doesn't need to get re-instantiated
|
||||||
// Every time the stage is changed.
|
// Every time the stage is changed.
|
||||||
property var printSetupSelector: Cura.PrintSetupSelector
|
property var printSetupSelector: Cura.PrintSetupSelector
|
||||||
{
|
{
|
||||||
width: UM.Theme.getSize("print_setup_widget").width
|
width: UM.Theme.getSize("print_setup_widget").width
|
||||||
height: UM.Theme.getSize("stage_menu").height
|
height: UM.Theme.getSize("stage_menu").height
|
||||||
headerCornerSide: RoundedRectangle.Direction.Right
|
headerCornerSide: RoundedRectangle.Direction.Right
|
||||||
|
onParentChanged:
|
||||||
|
{
|
||||||
|
if(stageMenu.oldParent !=null)
|
||||||
|
{
|
||||||
|
stageMenu.oldParent.Component.destruction.disconnect(stageMenu.onParentDestroyed)
|
||||||
|
}
|
||||||
|
stageMenu.oldParent = parent
|
||||||
|
visible = parent != stageMenu
|
||||||
|
parent.Component.destruction.connect(stageMenu.onParentDestroyed)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UM.MessageStack
|
UM.MessageStack
|
||||||
{
|
{
|
||||||
anchors
|
anchors
|
||||||
|
|
|
@ -11,6 +11,7 @@ import Cura 1.0 as Cura
|
||||||
|
|
||||||
UM.Dialog
|
UM.Dialog
|
||||||
{
|
{
|
||||||
|
id: base
|
||||||
title: catalog.i18nc("@title:window", "Save Project")
|
title: catalog.i18nc("@title:window", "Save Project")
|
||||||
|
|
||||||
minimumWidth: 500 * screenScaleFactor
|
minimumWidth: 500 * screenScaleFactor
|
||||||
|
@ -49,7 +50,7 @@ UM.Dialog
|
||||||
UM.SettingDefinitionsModel
|
UM.SettingDefinitionsModel
|
||||||
{
|
{
|
||||||
id: definitionsModel
|
id: definitionsModel
|
||||||
containerId: Cura.MachineManager.activeDefinitionId
|
containerId: base.visible ? Cura.MachineManager.activeDefinitionId: ""
|
||||||
showAll: true
|
showAll: true
|
||||||
exclude: ["command_line_settings"]
|
exclude: ["command_line_settings"]
|
||||||
showAncestors: true
|
showAncestors: true
|
||||||
|
|
|
@ -49,6 +49,7 @@ Item
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: index + 1
|
text: index + 1
|
||||||
font: UM.Theme.getFont("very_small")
|
font: UM.Theme.getFont("very_small")
|
||||||
|
color: UM.Theme.getColor("text")
|
||||||
width: contentWidth
|
width: contentWidth
|
||||||
height: contentHeight
|
height: contentHeight
|
||||||
visible: extruderEnabled
|
visible: extruderEnabled
|
||||||
|
|
|
@ -18,7 +18,7 @@ Item
|
||||||
property alias color: label.color
|
property alias color: label.color
|
||||||
property alias text: label.text
|
property alias text: label.text
|
||||||
property alias font: label.font
|
property alias font: label.font
|
||||||
|
property alias iconColor: icon.color
|
||||||
property real margin: UM.Theme.getSize("narrow_margin").width
|
property real margin: UM.Theme.getSize("narrow_margin").width
|
||||||
|
|
||||||
// These properties can be used in combination with layouts.
|
// These properties can be used in combination with layouts.
|
||||||
|
@ -39,7 +39,7 @@ Item
|
||||||
width: UM.Theme.getSize("section_icon").width
|
width: UM.Theme.getSize("section_icon").width
|
||||||
height: UM.Theme.getSize("section_icon").height
|
height: UM.Theme.getSize("section_icon").height
|
||||||
|
|
||||||
color: label.color
|
color: UM.Theme.getColor("icon")
|
||||||
|
|
||||||
anchors
|
anchors
|
||||||
{
|
{
|
||||||
|
|
|
@ -54,16 +54,23 @@ Item
|
||||||
{
|
{
|
||||||
text: model.name.toUpperCase()
|
text: model.name.toUpperCase()
|
||||||
checkable: true
|
checkable: true
|
||||||
checked: model.active
|
checked: UM.Controller.activeStage != null ? model.id == UM.Controller.activeStage.stageId : false
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
exclusiveGroup: mainWindowHeaderMenuGroup
|
exclusiveGroup: mainWindowHeaderMenuGroup
|
||||||
style: UM.Theme.styles.main_window_header_tab
|
style: UM.Theme.styles.main_window_header_tab
|
||||||
height: UM.Theme.getSize("main_window_header_button").height
|
height: UM.Theme.getSize("main_window_header_button").height
|
||||||
onClicked: UM.Controller.setActiveStage(model.id)
|
|
||||||
iconSource: model.stage.iconSource
|
iconSource: model.stage.iconSource
|
||||||
|
|
||||||
property color overlayColor: "transparent"
|
property color overlayColor: "transparent"
|
||||||
property string overlayIconSource: ""
|
property string overlayIconSource: ""
|
||||||
|
|
||||||
|
// This is a trick to assure the activeStage is correctly changed. It doesn't work propertly if done in the onClicked (see CURA-6028)
|
||||||
|
MouseArea
|
||||||
|
{
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: UM.Controller.setActiveStage(model.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|