Merge branch 'CURA-6005_cura_drive_plugin' of github.com:Ultimaker/Cura into CURA-6005_cura_drive_plugin

This commit is contained in:
Jaime van Kessel 2019-01-03 16:52:02 +01:00
commit 785f2a8a8b
103 changed files with 973 additions and 539 deletions

View File

@ -66,6 +66,11 @@ class Arrange:
continue continue
vertices = vertices.getMinkowskiHull(Polygon.approximatedCircle(min_offset)) vertices = vertices.getMinkowskiHull(Polygon.approximatedCircle(min_offset))
points = copy.deepcopy(vertices._points) points = copy.deepcopy(vertices._points)
# After scaling (like up to 0.1 mm) the node might not have points
if len(points) == 0:
continue
shape_arr = ShapeArray.fromPolygon(points, scale = scale) shape_arr = ShapeArray.fromPolygon(points, scale = scale)
arranger.place(0, 0, shape_arr) arranger.place(0, 0, shape_arr)

View File

@ -36,12 +36,12 @@ class CuraActions(QObject):
# Starting a web browser from a signal handler connected to a menu will crash on windows. # Starting a web browser from a signal handler connected to a menu will crash on windows.
# So instead, defer the call to the next run of the event loop, since that does work. # So instead, defer the call to the next run of the event loop, since that does work.
# Note that weirdly enough, only signal handlers that open a web browser fail like that. # Note that weirdly enough, only signal handlers that open a web browser fail like that.
event = CallFunctionEvent(self._openUrl, [QUrl("http://ultimaker.com/en/support/software")], {}) event = CallFunctionEvent(self._openUrl, [QUrl("https://ultimaker.com/en/resources/manuals/software")], {})
cura.CuraApplication.CuraApplication.getInstance().functionEvent(event) cura.CuraApplication.CuraApplication.getInstance().functionEvent(event)
@pyqtSlot() @pyqtSlot()
def openBugReportPage(self) -> None: def openBugReportPage(self) -> None:
event = CallFunctionEvent(self._openUrl, [QUrl("http://github.com/Ultimaker/Cura/issues")], {}) event = CallFunctionEvent(self._openUrl, [QUrl("https://github.com/Ultimaker/Cura/issues")], {})
cura.CuraApplication.CuraApplication.getInstance().functionEvent(event) cura.CuraApplication.CuraApplication.getInstance().functionEvent(event)
## Reset camera position and direction to default ## Reset camera position and direction to default

View File

@ -51,6 +51,7 @@ from cura.Arranging.ArrangeObjectsJob import ArrangeObjectsJob
from cura.Arranging.ArrangeObjectsAllBuildPlatesJob import ArrangeObjectsAllBuildPlatesJob from cura.Arranging.ArrangeObjectsAllBuildPlatesJob import ArrangeObjectsAllBuildPlatesJob
from cura.Arranging.ShapeArray import ShapeArray from cura.Arranging.ShapeArray import ShapeArray
from cura.MultiplyObjectsJob import MultiplyObjectsJob from cura.MultiplyObjectsJob import MultiplyObjectsJob
from cura.GlobalStacksModel import GlobalStacksModel
from cura.Scene.ConvexHullDecorator import ConvexHullDecorator from cura.Scene.ConvexHullDecorator import ConvexHullDecorator
from cura.Operations.SetParentOperation import SetParentOperation from cura.Operations.SetParentOperation import SetParentOperation
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
@ -113,6 +114,7 @@ from cura.Settings.CuraFormulaFunctions import CuraFormulaFunctions
from cura.ObjectsModel import ObjectsModel from cura.ObjectsModel import ObjectsModel
from cura.PrinterOutputDevice import PrinterOutputDevice
from cura.PrinterOutput.NetworkMJPGImage import NetworkMJPGImage from cura.PrinterOutput.NetworkMJPGImage import NetworkMJPGImage
from cura import ApplicationMetadata from cura import ApplicationMetadata
@ -499,7 +501,8 @@ class CuraApplication(QtApplication):
preferences.addPreference("cura/choice_on_profile_override", "always_ask") preferences.addPreference("cura/choice_on_profile_override", "always_ask")
preferences.addPreference("cura/choice_on_open_project", "always_ask") preferences.addPreference("cura/choice_on_open_project", "always_ask")
preferences.addPreference("cura/use_multi_build_plate", False) preferences.addPreference("cura/use_multi_build_plate", False)
preferences.addPreference("view/settings_list_height", 600)
preferences.addPreference("view/settings_visible", False)
preferences.addPreference("cura/currency", "") preferences.addPreference("cura/currency", "")
preferences.addPreference("cura/material_settings", "{}") preferences.addPreference("cura/material_settings", "{}")
@ -971,6 +974,7 @@ class CuraApplication(QtApplication):
qmlRegisterType(MultiBuildPlateModel, "Cura", 1, 0, "MultiBuildPlateModel") qmlRegisterType(MultiBuildPlateModel, "Cura", 1, 0, "MultiBuildPlateModel")
qmlRegisterType(InstanceContainer, "Cura", 1, 0, "InstanceContainer") qmlRegisterType(InstanceContainer, "Cura", 1, 0, "InstanceContainer")
qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel") qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel")
qmlRegisterType(GlobalStacksModel, "Cura", 1, 0, "GlobalStacksModel")
qmlRegisterType(FavoriteMaterialsModel, "Cura", 1, 0, "FavoriteMaterialsModel") qmlRegisterType(FavoriteMaterialsModel, "Cura", 1, 0, "FavoriteMaterialsModel")
qmlRegisterType(GenericMaterialsModel, "Cura", 1, 0, "GenericMaterialsModel") qmlRegisterType(GenericMaterialsModel, "Cura", 1, 0, "GenericMaterialsModel")
@ -992,6 +996,8 @@ class CuraApplication(QtApplication):
qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.getInstance) qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.getInstance)
qmlRegisterType(SidebarCustomMenuItemsModel, "Cura", 1, 0, "SidebarCustomMenuItemsModel") qmlRegisterType(SidebarCustomMenuItemsModel, "Cura", 1, 0, "SidebarCustomMenuItemsModel")
qmlRegisterType(PrinterOutputDevice, "Cura", 1, 0, "PrinterOutputDevice")
from cura.API import CuraAPI from cura.API import CuraAPI
qmlRegisterSingletonType(CuraAPI, "Cura", 1, 1, "API", self.getCuraAPI) qmlRegisterSingletonType(CuraAPI, "Cura", 1, 1, "API", self.getCuraAPI)

63
cura/GlobalStacksModel.py Normal file
View File

@ -0,0 +1,63 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Qt.ListModel import ListModel
from PyQt5.QtCore import pyqtProperty, Qt, pyqtSignal
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.ContainerStack import ContainerStack
from cura.PrinterOutputDevice import ConnectionType
from cura.Settings.GlobalStack import GlobalStack
class GlobalStacksModel(ListModel):
NameRole = Qt.UserRole + 1
IdRole = Qt.UserRole + 2
HasRemoteConnectionRole = Qt.UserRole + 3
ConnectionTypeRole = Qt.UserRole + 4
MetaDataRole = Qt.UserRole + 5
def __init__(self, parent = None):
super().__init__(parent)
self.addRoleName(self.NameRole, "name")
self.addRoleName(self.IdRole, "id")
self.addRoleName(self.HasRemoteConnectionRole, "hasRemoteConnection")
self.addRoleName(self.ConnectionTypeRole, "connectionType")
self.addRoleName(self.MetaDataRole, "metadata")
self._container_stacks = []
# Listen to changes
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerChanged)
ContainerRegistry.getInstance().containerMetaDataChanged.connect(self._onContainerChanged)
ContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChanged)
self._filter_dict = {}
self._update()
## Handler for container added/removed events from registry
def _onContainerChanged(self, container):
# We only need to update when the added / removed container GlobalStack
if isinstance(container, GlobalStack):
self._update()
def _update(self) -> None:
items = []
container_stacks = ContainerRegistry.getInstance().findContainerStacks(type = "machine")
for container_stack in container_stacks:
connection_type = int(container_stack.getMetaDataEntry("connection_type", ConnectionType.NotConnected.value))
has_remote_connection = connection_type in [ConnectionType.NetworkConnection.value, ConnectionType.CloudConnection.value]
if container_stack.getMetaDataEntry("hidden", False) in ["True", True]:
continue
# TODO: Remove reference to connect group name.
items.append({"name": container_stack.getMetaDataEntry("connect_group_name", container_stack.getName()),
"id": container_stack.getId(),
"hasRemoteConnection": has_remote_connection,
"connectionType": connection_type,
"metadata": container_stack.getMetaData().copy()})
items.sort(key=lambda i: not i["hasRemoteConnection"])
self.setItems(items)

View File

@ -302,6 +302,10 @@ class MaterialManager(QObject):
def getMaterialGroupListByGUID(self, guid: str) -> Optional[List[MaterialGroup]]: def getMaterialGroupListByGUID(self, guid: str) -> Optional[List[MaterialGroup]]:
return self._guid_material_groups_map.get(guid) return self._guid_material_groups_map.get(guid)
# Returns a dict of all material groups organized by root_material_id.
def getAllMaterialGroups(self) -> Dict[str, "MaterialGroup"]:
return self._material_group_map
# #
# Return a dict with all root material IDs (k) and ContainerNodes (v) that's suitable for the given setup. # Return a dict with all root material IDs (k) and ContainerNodes (v) that's suitable for the given setup.
# #

View File

@ -106,10 +106,7 @@ 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

View File

@ -1,10 +1,9 @@
# 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 UM.Logger import Logger
from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
## Model that shows the list of favorite materials.
class FavoriteMaterialsModel(BaseMaterialsModel): class FavoriteMaterialsModel(BaseMaterialsModel):
def __init__(self, parent = None): def __init__(self, parent = None):
super().__init__(parent) super().__init__(parent)

View File

@ -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.
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt, QTimer
from UM.Application import Application from UM.Application import Application
from UM.Logger import Logger from UM.Logger import Logger
@ -39,15 +39,23 @@ class QualityProfilesDropDownMenuModel(ListModel):
self._machine_manager = self._application.getMachineManager() self._machine_manager = self._application.getMachineManager()
self._quality_manager = Application.getInstance().getQualityManager() self._quality_manager = Application.getInstance().getQualityManager()
self._application.globalContainerStackChanged.connect(self._update) self._application.globalContainerStackChanged.connect(self._onChange)
self._machine_manager.activeQualityGroupChanged.connect(self._update) self._machine_manager.activeQualityGroupChanged.connect(self._onChange)
self._machine_manager.extruderChanged.connect(self._update) self._machine_manager.extruderChanged.connect(self._onChange)
self._quality_manager.qualitiesUpdated.connect(self._update) self._quality_manager.qualitiesUpdated.connect(self._onChange)
self._layer_height_unit = "" # This is cached self._layer_height_unit = "" # This is cached
self._update_timer = QTimer() # type: QTimer
self._update_timer.setInterval(100)
self._update_timer.setSingleShot(True)
self._update_timer.timeout.connect(self._update)
self._update() self._update()
def _onChange(self) -> None:
self._update_timer.start()
def _update(self): def _update(self):
Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__)) Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))

View File

@ -6,6 +6,7 @@ from typing import Optional, List
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
from UM.Logger import Logger from UM.Logger import Logger
from UM.Preferences import Preferences
from UM.Resources import Resources from UM.Resources import Resources
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
@ -18,14 +19,20 @@ class SettingVisibilityPresetsModel(QObject):
onItemsChanged = pyqtSignal() onItemsChanged = pyqtSignal()
activePresetChanged = pyqtSignal() activePresetChanged = pyqtSignal()
def __init__(self, preferences, parent = None): def __init__(self, preferences: Preferences, parent = None) -> None:
super().__init__(parent) super().__init__(parent)
self._items = [] # type: List[SettingVisibilityPreset] self._items = [] # type: List[SettingVisibilityPreset]
self._custom_preset = SettingVisibilityPreset(preset_id = "custom", name = "Custom selection", weight = -100)
self._populate() self._populate()
basic_item = self.getVisibilityPresetById("basic") basic_item = self.getVisibilityPresetById("basic")
basic_visibile_settings = ";".join(basic_item.settings) if basic_item is not None:
basic_visibile_settings = ";".join(basic_item.settings)
else:
Logger.log("w", "Unable to find the basic visiblity preset.")
basic_visibile_settings = ""
self._preferences = preferences self._preferences = preferences
@ -42,7 +49,8 @@ class SettingVisibilityPresetsModel(QObject):
visible_settings = self._preferences.getValue("general/visible_settings") visible_settings = self._preferences.getValue("general/visible_settings")
if not visible_settings: if not visible_settings:
self._preferences.setValue("general/visible_settings", ";".join(self._active_preset_item.settings)) new_visible_settings = self._active_preset_item.settings if self._active_preset_item is not None else []
self._preferences.setValue("general/visible_settings", ";".join(new_visible_settings))
else: else:
self._onPreferencesChanged("general/visible_settings") self._onPreferencesChanged("general/visible_settings")
@ -59,9 +67,7 @@ class SettingVisibilityPresetsModel(QObject):
def _populate(self) -> None: def _populate(self) -> None:
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
items = [] # type: List[SettingVisibilityPreset] items = [] # type: List[SettingVisibilityPreset]
items.append(self._custom_preset)
custom_preset = SettingVisibilityPreset(preset_id="custom", name ="Custom selection", weight = -100)
items.append(custom_preset)
for file_path in Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.SettingVisibilityPreset): for file_path in Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.SettingVisibilityPreset):
setting_visibility_preset = SettingVisibilityPreset() setting_visibility_preset = SettingVisibilityPreset()
try: try:
@ -77,7 +83,7 @@ class SettingVisibilityPresetsModel(QObject):
self.setItems(items) self.setItems(items)
@pyqtProperty("QVariantList", notify = onItemsChanged) @pyqtProperty("QVariantList", notify = onItemsChanged)
def items(self): def items(self) -> List[SettingVisibilityPreset]:
return self._items return self._items
def setItems(self, items: List[SettingVisibilityPreset]) -> None: def setItems(self, items: List[SettingVisibilityPreset]) -> None:
@ -87,7 +93,7 @@ class SettingVisibilityPresetsModel(QObject):
@pyqtSlot(str) @pyqtSlot(str)
def setActivePreset(self, preset_id: str) -> None: def setActivePreset(self, preset_id: str) -> None:
if preset_id == self._active_preset_item.presetId: if self._active_preset_item is not None and preset_id == self._active_preset_item.presetId:
Logger.log("d", "Same setting visibility preset [%s] selected, do nothing.", preset_id) Logger.log("d", "Same setting visibility preset [%s] selected, do nothing.", preset_id)
return return
@ -96,7 +102,7 @@ class SettingVisibilityPresetsModel(QObject):
Logger.log("w", "Tried to set active preset to unknown id [%s]", preset_id) Logger.log("w", "Tried to set active preset to unknown id [%s]", preset_id)
return return
need_to_save_to_custom = self._active_preset_item.presetId == "custom" and preset_id != "custom" need_to_save_to_custom = self._active_preset_item is None or (self._active_preset_item.presetId == "custom" and preset_id != "custom")
if need_to_save_to_custom: if need_to_save_to_custom:
# Save the current visibility settings to custom # Save the current visibility settings to custom
current_visibility_string = self._preferences.getValue("general/visible_settings") current_visibility_string = self._preferences.getValue("general/visible_settings")
@ -117,7 +123,9 @@ class SettingVisibilityPresetsModel(QObject):
@pyqtProperty(str, notify = activePresetChanged) @pyqtProperty(str, notify = activePresetChanged)
def activePreset(self) -> str: def activePreset(self) -> str:
return self._active_preset_item.presetId if self._active_preset_item is not None:
return self._active_preset_item.presetId
return ""
def _onPreferencesChanged(self, name: str) -> None: def _onPreferencesChanged(self, name: str) -> None:
if name != "general/visible_settings": if name != "general/visible_settings":
@ -149,7 +157,12 @@ class SettingVisibilityPresetsModel(QObject):
else: else:
item_to_set = matching_preset_item item_to_set = matching_preset_item
# If we didn't find a matching preset, fallback to custom.
if item_to_set is None:
item_to_set = self._custom_preset
if self._active_preset_item is None or self._active_preset_item.presetId != item_to_set.presetId: if self._active_preset_item is None or self._active_preset_item.presetId != item_to_set.presetId:
self._active_preset_item = item_to_set self._active_preset_item = item_to_set
self._preferences.setValue("cura/active_setting_visibility_preset", self._active_preset_item.presetId) if self._active_preset_item is not None:
self._preferences.setValue("cura/active_setting_visibility_preset", self._active_preset_item.presetId)
self.activePresetChanged.emit() self.activePresetChanged.emit()

View File

@ -83,9 +83,11 @@ class AuthorizationService:
if not self.getUserProfile(): if not self.getUserProfile():
# We check if we can get the user profile. # We check if we can get the user profile.
# If we can't get it, that means the access token (JWT) was invalid or expired. # If we can't get it, that means the access token (JWT) was invalid or expired.
Logger.log("w", "Unable to get the user profile.")
return None return None
if self._auth_data is None: if self._auth_data is None:
Logger.log("d", "No auth data to retrieve the access_token from")
return None return None
return self._auth_data.access_token return self._auth_data.access_token

View File

@ -6,7 +6,7 @@ from UM.Logger import Logger
from UM.Scene.SceneNode import SceneNode #For typing. from UM.Scene.SceneNode import SceneNode #For typing.
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState, ConnectionType
from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply, QAuthenticator from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply, QAuthenticator
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl, QCoreApplication from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl, QCoreApplication
@ -28,8 +28,8 @@ class AuthState(IntEnum):
class NetworkedPrinterOutputDevice(PrinterOutputDevice): class NetworkedPrinterOutputDevice(PrinterOutputDevice):
authenticationStateChanged = pyqtSignal() authenticationStateChanged = pyqtSignal()
def __init__(self, device_id, address: str, properties: Dict[bytes, bytes], parent: QObject = None) -> None: def __init__(self, device_id, address: str, properties: Dict[bytes, bytes], connection_type: ConnectionType = ConnectionType.NetworkConnection, parent: QObject = None) -> None:
super().__init__(device_id = device_id, parent = parent) super().__init__(device_id = device_id, connection_type = connection_type, parent = parent)
self._manager = None # type: Optional[QNetworkAccessManager] self._manager = None # type: Optional[QNetworkAccessManager]
self._last_manager_create_time = None # type: Optional[float] self._last_manager_create_time = None # type: Optional[float]
self._recreate_network_manager_time = 30 self._recreate_network_manager_time = 30
@ -125,7 +125,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
if self._connection_state_before_timeout is None: if self._connection_state_before_timeout is None:
self._connection_state_before_timeout = self._connection_state self._connection_state_before_timeout = self._connection_state
self.setConnectionState(ConnectionState.closed) self.setConnectionState(ConnectionState.Closed)
# We need to check if the manager needs to be re-created. If we don't, we get some issues when OSX goes to # We need to check if the manager needs to be re-created. If we don't, we get some issues when OSX goes to
# sleep. # sleep.
@ -133,7 +133,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
if self._last_manager_create_time is None or time() - self._last_manager_create_time > self._recreate_network_manager_time: if self._last_manager_create_time is None or time() - self._last_manager_create_time > self._recreate_network_manager_time:
self._createNetworkManager() self._createNetworkManager()
assert(self._manager is not None) assert(self._manager is not None)
elif self._connection_state == ConnectionState.closed: elif self._connection_state == ConnectionState.Closed:
# Go out of timeout. # Go out of timeout.
if self._connection_state_before_timeout is not None: # sanity check, but it should never be None here if self._connection_state_before_timeout is not None: # sanity check, but it should never be None here
self.setConnectionState(self._connection_state_before_timeout) self.setConnectionState(self._connection_state_before_timeout)
@ -285,8 +285,8 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
self._last_response_time = time() self._last_response_time = time()
if self._connection_state == ConnectionState.connecting: if self._connection_state == ConnectionState.Connecting:
self.setConnectionState(ConnectionState.connected) self.setConnectionState(ConnectionState.Connected)
callback_key = reply.url().toString() + str(reply.operation()) callback_key = reply.url().toString() + str(reply.operation())
try: try:

View File

@ -4,7 +4,7 @@
from UM.Decorators import deprecated from UM.Decorators import deprecated
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
from UM.OutputDevice.OutputDevice import OutputDevice from UM.OutputDevice.OutputDevice import OutputDevice
from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject, QTimer, QUrl from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject, QTimer, QUrl, Q_ENUMS
from PyQt5.QtWidgets import QMessageBox from PyQt5.QtWidgets import QMessageBox
from UM.Logger import Logger from UM.Logger import Logger
@ -28,11 +28,18 @@ i18n_catalog = i18nCatalog("cura")
## The current processing state of the backend. ## The current processing state of the backend.
class ConnectionState(IntEnum): class ConnectionState(IntEnum):
closed = 0 Closed = 0
connecting = 1 Connecting = 1
connected = 2 Connected = 2
busy = 3 Busy = 3
error = 4 Error = 4
class ConnectionType(IntEnum):
NotConnected = 0
UsbConnection = 1
NetworkConnection = 2
CloudConnection = 3
## Printer output device adds extra interface options on top of output device. ## Printer output device adds extra interface options on top of output device.
@ -46,6 +53,11 @@ class ConnectionState(IntEnum):
# For all other uses it should be used in the same way as a "regular" OutputDevice. # For all other uses it should be used in the same way as a "regular" OutputDevice.
@signalemitter @signalemitter
class PrinterOutputDevice(QObject, OutputDevice): class PrinterOutputDevice(QObject, OutputDevice):
# Put ConnectionType here with Q_ENUMS() so it can be registered as a QML type and accessible via QML, and there is
# no need to remember what those Enum integer values mean.
Q_ENUMS(ConnectionType)
printersChanged = pyqtSignal() printersChanged = pyqtSignal()
connectionStateChanged = pyqtSignal(str) connectionStateChanged = pyqtSignal(str)
acceptsCommandsChanged = pyqtSignal() acceptsCommandsChanged = pyqtSignal()
@ -62,7 +74,7 @@ class PrinterOutputDevice(QObject, OutputDevice):
# Signal to indicate that the configuration of one of the printers has changed. # Signal to indicate that the configuration of one of the printers has changed.
uniqueConfigurationsChanged = pyqtSignal() uniqueConfigurationsChanged = pyqtSignal()
def __init__(self, device_id: str, parent: QObject = None) -> None: def __init__(self, device_id: str, connection_type: "ConnectionType" = ConnectionType.NotConnected, parent: QObject = None) -> None:
super().__init__(device_id = device_id, parent = parent) # type: ignore # MyPy complains with the multiple inheritance super().__init__(device_id = device_id, parent = parent) # type: ignore # MyPy complains with the multiple inheritance
self._printers = [] # type: List[PrinterOutputModel] self._printers = [] # type: List[PrinterOutputModel]
@ -83,7 +95,8 @@ class PrinterOutputDevice(QObject, OutputDevice):
self._update_timer.setSingleShot(False) self._update_timer.setSingleShot(False)
self._update_timer.timeout.connect(self._update) self._update_timer.timeout.connect(self._update)
self._connection_state = ConnectionState.closed #type: ConnectionState self._connection_state = ConnectionState.Closed #type: ConnectionState
self._connection_type = connection_type
self._firmware_updater = None #type: Optional[FirmwareUpdater] self._firmware_updater = None #type: Optional[FirmwareUpdater]
self._firmware_name = None #type: Optional[str] self._firmware_name = None #type: Optional[str]
@ -110,15 +123,18 @@ class PrinterOutputDevice(QObject, OutputDevice):
callback(QMessageBox.Yes) callback(QMessageBox.Yes)
def isConnected(self) -> bool: def isConnected(self) -> bool:
return self._connection_state != ConnectionState.closed and self._connection_state != ConnectionState.error return self._connection_state != ConnectionState.Closed and self._connection_state != ConnectionState.Error
def setConnectionState(self, connection_state: ConnectionState) -> None: def setConnectionState(self, connection_state: "ConnectionState") -> None:
if self._connection_state != connection_state: if self._connection_state != connection_state:
self._connection_state = connection_state self._connection_state = connection_state
self.connectionStateChanged.emit(self._id) self.connectionStateChanged.emit(self._id)
def getConnectionType(self) -> "ConnectionType":
return self._connection_type
@pyqtProperty(str, notify = connectionStateChanged) @pyqtProperty(str, notify = connectionStateChanged)
def connectionState(self) -> ConnectionState: def connectionState(self) -> "ConnectionState":
return self._connection_state return self._connection_state
def _update(self) -> None: def _update(self) -> None:
@ -174,13 +190,13 @@ class PrinterOutputDevice(QObject, OutputDevice):
## Attempt to establish connection ## Attempt to establish connection
def connect(self) -> None: def connect(self) -> None:
self.setConnectionState(ConnectionState.connecting) self.setConnectionState(ConnectionState.Connecting)
self._update_timer.start() self._update_timer.start()
## Attempt to close the connection ## Attempt to close the connection
def close(self) -> None: def close(self) -> None:
self._update_timer.stop() self._update_timer.stop()
self.setConnectionState(ConnectionState.closed) self.setConnectionState(ConnectionState.Closed)
## Ensure that close gets called when object is destroyed ## Ensure that close gets called when object is destroyed
def __del__(self) -> None: def __del__(self) -> None:
@ -214,7 +230,7 @@ class PrinterOutputDevice(QObject, OutputDevice):
# Returns the unique configurations of the printers within this output device # Returns the unique configurations of the printers within this output device
@pyqtProperty("QStringList", notify = uniqueConfigurationsChanged) @pyqtProperty("QStringList", notify = uniqueConfigurationsChanged)
def uniquePrinterTypes(self) -> List[str]: def uniquePrinterTypes(self) -> List[str]:
return list(set([configuration.printerType for configuration in self._unique_configurations])) return list(sorted(set([configuration.printerType for configuration in self._unique_configurations])))
def _onPrintersChanged(self) -> None: def _onPrintersChanged(self) -> None:
for printer in self._printers: for printer in self._printers:

View File

@ -23,7 +23,7 @@ from UM.Settings.SettingFunction import SettingFunction
from UM.Signal import postponeSignals, CompressTechnique from UM.Signal import postponeSignals, CompressTechnique
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
from cura.PrinterOutputDevice import PrinterOutputDevice from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionType
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel
from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
@ -299,6 +299,7 @@ class MachineManager(QObject):
self.activeMaterialChanged.emit() self.activeMaterialChanged.emit()
self.rootMaterialChanged.emit() self.rootMaterialChanged.emit()
self.numberExtrudersEnabledChanged.emit()
def _onContainersChanged(self, container: ContainerInterface) -> None: def _onContainersChanged(self, container: ContainerInterface) -> None:
self._instance_container_timer.start() self._instance_container_timer.start()
@ -523,7 +524,13 @@ class MachineManager(QObject):
def printerConnected(self): def printerConnected(self):
return bool(self._printer_output_devices) return bool(self._printer_output_devices)
@pyqtProperty(str, notify = printerConnectedStatusChanged) @pyqtProperty(bool, notify = printerConnectedStatusChanged)
def activeMachineHasRemoteConnection(self) -> bool:
if self._global_container_stack:
connection_type = int(self._global_container_stack.getMetaDataEntry("connection_type", ConnectionType.NotConnected.value))
return connection_type in [ConnectionType.NetworkConnection.value, ConnectionType.CloudConnection.value]
return False
def activeMachineNetworkKey(self) -> str: def activeMachineNetworkKey(self) -> str:
if self._global_container_stack: if self._global_container_stack:
return self._global_container_stack.getMetaDataEntry("um_network_key", "") return self._global_container_stack.getMetaDataEntry("um_network_key", "")
@ -751,7 +758,7 @@ class MachineManager(QObject):
self.setActiveMachine(other_machine_stacks[0]["id"]) self.setActiveMachine(other_machine_stacks[0]["id"])
metadata = CuraContainerRegistry.getInstance().findContainerStacksMetadata(id = machine_id)[0] metadata = CuraContainerRegistry.getInstance().findContainerStacksMetadata(id = machine_id)[0]
network_key = metadata["um_network_key"] if "um_network_key" in metadata else None network_key = metadata.get("um_network_key", None)
ExtruderManager.getInstance().removeMachineExtruders(machine_id) ExtruderManager.getInstance().removeMachineExtruders(machine_id)
containers = CuraContainerRegistry.getInstance().findInstanceContainersMetadata(type = "user", machine = machine_id) containers = CuraContainerRegistry.getInstance().findInstanceContainersMetadata(type = "user", machine = machine_id)
for container in containers: for container in containers:
@ -1317,17 +1324,18 @@ class MachineManager(QObject):
# Get the definition id corresponding to this machine name # Get the definition id corresponding to this machine name
machine_definition_id = CuraContainerRegistry.getInstance().findDefinitionContainers(name = machine_name)[0].getId() machine_definition_id = CuraContainerRegistry.getInstance().findDefinitionContainers(name = machine_name)[0].getId()
# Try to find a machine with the same network key # Try to find a machine with the same network key
new_machine = self.getMachine(machine_definition_id, metadata_filter = {"um_network_key": self.activeMachineNetworkKey}) new_machine = self.getMachine(machine_definition_id, metadata_filter = {"um_network_key": self.activeMachineNetworkKey()})
# If there is no machine, then create a new one and set it to the non-hidden instance # If there is no machine, then create a new one and set it to the non-hidden instance
if not new_machine: if not new_machine:
new_machine = CuraStackBuilder.createMachine(machine_definition_id + "_sync", machine_definition_id) new_machine = CuraStackBuilder.createMachine(machine_definition_id + "_sync", machine_definition_id)
if not new_machine: if not new_machine:
return return
new_machine.setMetaDataEntry("um_network_key", self.activeMachineNetworkKey) new_machine.setMetaDataEntry("um_network_key", self.activeMachineNetworkKey())
new_machine.setMetaDataEntry("connect_group_name", self.activeMachineNetworkGroupName) new_machine.setMetaDataEntry("connect_group_name", self.activeMachineNetworkGroupName)
new_machine.setMetaDataEntry("hidden", False) new_machine.setMetaDataEntry("hidden", False)
new_machine.setMetaDataEntry("connection_type", self._global_container_stack.getMetaDataEntry("connection_type"))
else: else:
Logger.log("i", "Found a %s with the key %s. Let's use it!", machine_name, self.activeMachineNetworkKey) Logger.log("i", "Found a %s with the key %s. Let's use it!", machine_name, self.activeMachineNetworkKey())
new_machine.setMetaDataEntry("hidden", False) new_machine.setMetaDataEntry("hidden", False)
# Set the current printer instance to hidden (the metadata entry must exist) # Set the current printer instance to hidden (the metadata entry must exist)
@ -1387,10 +1395,10 @@ class MachineManager(QObject):
# After updating from 3.2 to 3.3 some group names may be temporary. If there is a mismatch in the name of the group # After updating from 3.2 to 3.3 some group names may be temporary. If there is a mismatch in the name of the group
# then all the container stacks are updated, both the current and the hidden ones. # then all the container stacks are updated, both the current and the hidden ones.
def checkCorrectGroupName(self, device_id: str, group_name: str) -> None: def checkCorrectGroupName(self, device_id: str, group_name: str) -> None:
if self._global_container_stack and device_id == self.activeMachineNetworkKey: if self._global_container_stack and device_id == self.activeMachineNetworkKey():
# Check if the connect_group_name is correct. If not, update all the containers connected to the same printer # Check if the connect_group_name is correct. If not, update all the containers connected to the same printer
if self.activeMachineNetworkGroupName != group_name: if self.activeMachineNetworkGroupName != group_name:
metadata_filter = {"um_network_key": self.activeMachineNetworkKey} metadata_filter = {"um_network_key": self.activeMachineNetworkKey()}
containers = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter) containers = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
for container in containers: for container in containers:
container.setMetaDataEntry("connect_group_name", group_name) container.setMetaDataEntry("connect_group_name", group_name)
@ -1529,6 +1537,10 @@ class MachineManager(QObject):
def activeQualityChangesGroup(self) -> Optional["QualityChangesGroup"]: def activeQualityChangesGroup(self) -> Optional["QualityChangesGroup"]:
return self._current_quality_changes_group return self._current_quality_changes_group
@pyqtProperty(bool, notify = activeQualityChangesGroupChanged)
def hasCustomQuality(self) -> bool:
return self._current_quality_changes_group is not None
@pyqtProperty(str, notify = activeQualityGroupChanged) @pyqtProperty(str, notify = activeQualityGroupChanged)
def activeQualityOrQualityChangesName(self) -> str: def activeQualityOrQualityChangesName(self) -> str:
name = empty_quality_container.getName() name = empty_quality_container.getName()

View File

@ -17,15 +17,11 @@ class SimpleModeSettingsManager(QObject):
self._is_profile_user_created = False # True when profile was custom created by user self._is_profile_user_created = False # True when profile was custom created by user
self._machine_manager.activeStackValueChanged.connect(self._updateIsProfileCustomized) self._machine_manager.activeStackValueChanged.connect(self._updateIsProfileCustomized)
self._machine_manager.activeQualityGroupChanged.connect(self.updateIsProfileUserCreated)
self._machine_manager.activeQualityChangesGroupChanged.connect(self.updateIsProfileUserCreated)
# update on create as the activeQualityChanged signal is emitted before this manager is created when Cura starts # update on create as the activeQualityChanged signal is emitted before this manager is created when Cura starts
self._updateIsProfileCustomized() self._updateIsProfileCustomized()
self.updateIsProfileUserCreated()
isProfileCustomizedChanged = pyqtSignal() isProfileCustomizedChanged = pyqtSignal()
isProfileUserCreatedChanged = pyqtSignal()
@pyqtProperty(bool, notify = isProfileCustomizedChanged) @pyqtProperty(bool, notify = isProfileCustomizedChanged)
def isProfileCustomized(self): def isProfileCustomized(self):
@ -58,34 +54,6 @@ class SimpleModeSettingsManager(QObject):
self._is_profile_customized = has_customized_user_settings self._is_profile_customized = has_customized_user_settings
self.isProfileCustomizedChanged.emit() self.isProfileCustomizedChanged.emit()
@pyqtProperty(bool, notify = isProfileUserCreatedChanged)
def isProfileUserCreated(self):
return self._is_profile_user_created
@pyqtSlot()
def updateIsProfileUserCreated(self) -> None:
quality_changes_keys = set() # type: Set[str]
if not self._machine_manager.activeMachine:
return
global_stack = self._machine_manager.activeMachine
# check quality changes settings in the global stack
quality_changes_keys.update(global_stack.qualityChanges.getAllKeys())
# check quality changes settings in the extruder stacks
if global_stack.extruders:
for extruder_stack in global_stack.extruders.values():
quality_changes_keys.update(extruder_stack.qualityChanges.getAllKeys())
# check if the qualityChanges container is not empty (meaning it is a user created profile)
has_quality_changes = len(quality_changes_keys) > 0
if has_quality_changes != self._is_profile_user_created:
self._is_profile_user_created = has_quality_changes
self.isProfileUserCreatedChanged.emit()
# These are the settings included in the Simple ("Recommended") Mode, so only when the other settings have been # These are the settings included in the Simple ("Recommended") Mode, so only when the other settings have been
# changed, we consider it as a user customized profile in the Simple ("Recommended") Mode. # changed, we consider it as a user customized profile in the Simple ("Recommended") Mode.
__ignored_custom_setting_keys = ["support_enable", __ignored_custom_setting_keys = ["support_enable",

View File

@ -794,7 +794,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# Clear all existing containers # Clear all existing containers
quality_changes_info.global_info.container.clear() quality_changes_info.global_info.container.clear()
for container_info in quality_changes_info.extruder_info_dict.values(): for container_info in quality_changes_info.extruder_info_dict.values():
container_info.container.clear() if container_info.container:
container_info.container.clear()
# Loop over everything and override the existing containers # Loop over everything and override the existing containers
global_info = quality_changes_info.global_info global_info = quality_changes_info.global_info

View File

@ -35,7 +35,7 @@ RowLayout
busy: CuraDrive.isCreatingBackup busy: CuraDrive.isCreatingBackup
} }
Cura.CheckBox Cura.CheckBoxWithTooltip
{ {
id: autoBackupEnabled id: autoBackupEnabled
checked: CuraDrive.autoBackupEnabled checked: CuraDrive.autoBackupEnabled

View File

@ -86,8 +86,8 @@ class CuraEngineBackend(QObject, Backend):
self._layer_view_active = False #type: bool self._layer_view_active = False #type: bool
self._onActiveViewChanged() self._onActiveViewChanged()
self._stored_layer_data = [] #type: List[Arcus.PythonMessage] self._stored_layer_data = [] # type: List[Arcus.PythonMessage]
self._stored_optimized_layer_data = {} #type: Dict[int, List[Arcus.PythonMessage]] # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob self._stored_optimized_layer_data = {} # type: Dict[int, List[Arcus.PythonMessage]] # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob
self._scene = self._application.getController().getScene() #type: Scene self._scene = self._application.getController().getScene() #type: Scene
self._scene.sceneChanged.connect(self._onSceneChanged) self._scene.sceneChanged.connect(self._onSceneChanged)
@ -229,6 +229,7 @@ class CuraEngineBackend(QObject, Backend):
if not self._build_plates_to_be_sliced: if not self._build_plates_to_be_sliced:
self.processingProgress.emit(1.0) self.processingProgress.emit(1.0)
Logger.log("w", "Slice unnecessary, nothing has changed that needs reslicing.") Logger.log("w", "Slice unnecessary, nothing has changed that needs reslicing.")
self.setState(BackendState.Done)
return return
if self._process_layers_job: if self._process_layers_job:
@ -245,7 +246,7 @@ class CuraEngineBackend(QObject, Backend):
num_objects = self._numObjectsPerBuildPlate() num_objects = self._numObjectsPerBuildPlate()
self._stored_layer_data = [] self._stored_layer_data = []
self._stored_optimized_layer_data[build_plate_to_be_sliced] = []
if build_plate_to_be_sliced not in num_objects or num_objects[build_plate_to_be_sliced] == 0: if build_plate_to_be_sliced not in num_objects or num_objects[build_plate_to_be_sliced] == 0:
self._scene.gcode_dict[build_plate_to_be_sliced] = [] #type: ignore #Because we created this attribute above. self._scene.gcode_dict[build_plate_to_be_sliced] = [] #type: ignore #Because we created this attribute above.
@ -253,7 +254,7 @@ class CuraEngineBackend(QObject, Backend):
if self._build_plates_to_be_sliced: if self._build_plates_to_be_sliced:
self.slice() self.slice()
return return
self._stored_optimized_layer_data[build_plate_to_be_sliced] = []
if self._application.getPrintInformation() and build_plate_to_be_sliced == active_build_plate: if self._application.getPrintInformation() and build_plate_to_be_sliced == active_build_plate:
self._application.getPrintInformation().setToZeroPrintInformation(build_plate_to_be_sliced) self._application.getPrintInformation().setToZeroPrintInformation(build_plate_to_be_sliced)
@ -410,7 +411,7 @@ class CuraEngineBackend(QObject, Backend):
if job.getResult() == StartJobResult.NothingToSlice: if job.getResult() == StartJobResult.NothingToSlice:
if self._application.platformActivity: if self._application.platformActivity:
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 or are assigned to a disabled extruder. Please scale or rotate models to fit, or enable an extruder."),
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.setState(BackendState.Error) self.setState(BackendState.Error)

View File

@ -13,7 +13,8 @@ import Cura 1.0 as Cura
Cura.MachineAction Cura.MachineAction
{ {
id: base id: base
property var extrudersModel: CuraApplication.getExtrudersModel() property var extrudersModel: Cura.ExtrudersModel{} // Do not retrieve the Model from a backend. Otherwise the tabs
// in tabView will not removed/updated. Probably QML bug
property int extruderTabsCount: 0 property int extruderTabsCount: 0
property var activeMachineId: Cura.MachineManager.activeMachine != null ? Cura.MachineManager.activeMachine.id : "" property var activeMachineId: Cura.MachineManager.activeMachine != null ? Cura.MachineManager.activeMachine.id : ""

View File

@ -16,12 +16,20 @@ Item
color: UM.Theme.getColor("viewport_overlay") color: UM.Theme.getColor("viewport_overlay")
anchors.fill: parent anchors.fill: parent
// This mouse area is to prevent mouse clicks to be passed onto the scene.
MouseArea MouseArea
{ {
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.AllButtons acceptedButtons: Qt.AllButtons
onWheel: wheel.accepted = true onWheel: wheel.accepted = true
} }
// Disable dropping files into Cura when the monitor page is active
DropArea
{
anchors.fill: parent
}
} }
Loader Loader

View File

@ -25,7 +25,7 @@ UM.Dialog
{ {
if(!visible) //Whenever the window is closed (either via the "Close" button or the X on the window frame), we want to update it in the stack. if(!visible) //Whenever the window is closed (either via the "Close" button or the X on the window frame), we want to update it in the stack.
{ {
manager.writeScriptsToStack(); manager.writeScriptsToStack()
} }
} }
@ -61,18 +61,23 @@ UM.Dialog
anchors.leftMargin: base.textMargin anchors.leftMargin: base.textMargin
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: base.textMargin anchors.rightMargin: base.textMargin
font: UM.Theme.getFont("large") font: UM.Theme.getFont("large_bold")
elide: Text.ElideRight elide: Text.ElideRight
} }
ListView ListView
{ {
id: activeScriptsList id: activeScriptsList
anchors.top: activeScriptsHeader.bottom
anchors.topMargin: base.textMargin anchors
anchors.left: parent.left {
anchors.leftMargin: UM.Theme.getSize("default_margin").width top: activeScriptsHeader.bottom
anchors.right: parent.right left: parent.left
anchors.rightMargin: base.textMargin right: parent.right
rightMargin: base.textMargin
topMargin: base.textMargin
leftMargin: UM.Theme.getSize("default_margin").width
}
height: childrenRect.height height: childrenRect.height
model: manager.scriptList model: manager.scriptList
delegate: Item delegate: Item
@ -84,8 +89,12 @@ UM.Dialog
id: activeScriptButton id: activeScriptButton
text: manager.getScriptLabelByKey(modelData.toString()) text: manager.getScriptLabelByKey(modelData.toString())
exclusiveGroup: selectedScriptGroup exclusiveGroup: selectedScriptGroup
width: parent.width
height: UM.Theme.getSize("setting").height
checkable: true checkable: true
checked: {
checked:
{
if (manager.selectedScriptIndex == index) if (manager.selectedScriptIndex == index)
{ {
base.activeScriptName = manager.getScriptLabelByKey(modelData.toString()) base.activeScriptName = manager.getScriptLabelByKey(modelData.toString())
@ -102,8 +111,7 @@ UM.Dialog
manager.setSelectedScriptIndex(index) manager.setSelectedScriptIndex(index)
base.activeScriptName = manager.getScriptLabelByKey(modelData.toString()) base.activeScriptName = manager.getScriptLabelByKey(modelData.toString())
} }
width: parent.width
height: UM.Theme.getSize("setting").height
style: ButtonStyle style: ButtonStyle
{ {
background: Rectangle background: Rectangle
@ -121,6 +129,7 @@ UM.Dialog
} }
} }
} }
Button Button
{ {
id: removeButton id: removeButton
@ -249,8 +258,8 @@ UM.Dialog
onTriggered: manager.addScriptToList(modelData.toString()) onTriggered: manager.addScriptToList(modelData.toString())
} }
onObjectAdded: scriptsMenu.insertItem(index, object); onObjectAdded: scriptsMenu.insertItem(index, object)
onObjectRemoved: scriptsMenu.removeItem(object); onObjectRemoved: scriptsMenu.removeItem(object)
} }
} }
} }
@ -268,26 +277,35 @@ UM.Dialog
{ {
id: scriptSpecsHeader id: scriptSpecsHeader
text: manager.selectedScriptIndex == -1 ? catalog.i18nc("@label", "Settings") : base.activeScriptName text: manager.selectedScriptIndex == -1 ? catalog.i18nc("@label", "Settings") : base.activeScriptName
anchors.top: parent.top anchors
anchors.topMargin: base.textMargin {
anchors.left: parent.left top: parent.top
anchors.leftMargin: base.textMargin topMargin: base.textMargin
anchors.right: parent.right left: parent.left
anchors.rightMargin: base.textMargin leftMargin: base.textMargin
right: parent.right
rightMargin: base.textMargin
}
elide: Text.ElideRight elide: Text.ElideRight
height: 20 * screenScaleFactor height: 20 * screenScaleFactor
font: UM.Theme.getFont("large") font: UM.Theme.getFont("large_bold")
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
} }
ScrollView ScrollView
{ {
id: scrollView id: scrollView
anchors.top: scriptSpecsHeader.bottom anchors
anchors.topMargin: settingsPanel.textMargin {
anchors.left: parent.left top: scriptSpecsHeader.bottom
anchors.right: parent.right topMargin: settingsPanel.textMargin
anchors.bottom: parent.bottom left: parent.left
leftMargin: UM.Theme.getSize("default_margin").width
right: parent.right
bottom: parent.bottom
}
visible: manager.selectedScriptDefinitionId != "" visible: manager.selectedScriptDefinitionId != ""
style: UM.Theme.styles.scrollview; style: UM.Theme.styles.scrollview;
@ -297,11 +315,12 @@ UM.Dialog
spacing: UM.Theme.getSize("default_lining").height spacing: UM.Theme.getSize("default_lining").height
model: UM.SettingDefinitionsModel model: UM.SettingDefinitionsModel
{ {
id: definitionsModel; id: definitionsModel
containerId: manager.selectedScriptDefinitionId containerId: manager.selectedScriptDefinitionId
showAll: true showAll: true
} }
delegate:Loader
delegate: Loader
{ {
id: settingLoader id: settingLoader
@ -312,23 +331,24 @@ UM.Dialog
{ {
if(model.type != undefined) if(model.type != undefined)
{ {
return UM.Theme.getSize("section").height; return UM.Theme.getSize("section").height
} }
else else
{ {
return 0; return 0
} }
} }
else else
{ {
return 0; return 0
} }
} }
Behavior on height { NumberAnimation { duration: 100 } } Behavior on height { NumberAnimation { duration: 100 } }
opacity: provider.properties.enabled == "True" ? 1 : 0 opacity: provider.properties.enabled == "True" ? 1 : 0
Behavior on opacity { NumberAnimation { duration: 100 } } Behavior on opacity { NumberAnimation { duration: 100 } }
enabled: opacity > 0 enabled: opacity > 0
property var definition: model property var definition: model
property var settingDefinitionsModel: definitionsModel property var settingDefinitionsModel: definitionsModel
property var propertyProvider: provider property var propertyProvider: provider
@ -339,11 +359,12 @@ UM.Dialog
//causing nasty issues when selecting different options. So disable asynchronous loading of enum type completely. //causing nasty issues when selecting different options. So disable asynchronous loading of enum type completely.
asynchronous: model.type != "enum" && model.type != "extruder" asynchronous: model.type != "enum" && model.type != "extruder"
onLoaded: { onLoaded:
{
settingLoader.item.showRevertButton = false settingLoader.item.showRevertButton = false
settingLoader.item.showInheritButton = false settingLoader.item.showInheritButton = false
settingLoader.item.showLinkedSettingIcon = false settingLoader.item.showLinkedSettingIcon = false
settingLoader.item.doDepthIndentation = true settingLoader.item.doDepthIndentation = false
settingLoader.item.doQualityUserSettingEmphasis = false settingLoader.item.doQualityUserSettingEmphasis = false
} }
@ -395,18 +416,14 @@ UM.Dialog
onShowTooltip: onShowTooltip:
{ {
tooltip.text = text; tooltip.text = text
var position = settingLoader.mapToItem(settingsPanel, settingsPanel.x, 0); var position = settingLoader.mapToItem(settingsPanel, settingsPanel.x, 0)
tooltip.show(position); tooltip.show(position)
tooltip.target.x = position.x + 1 tooltip.target.x = position.x + 1
} }
onHideTooltip: onHideTooltip: tooltip.hide()
{
tooltip.hide();
}
} }
} }
} }
} }
@ -459,6 +476,7 @@ UM.Dialog
Cura.SettingUnknown { } Cura.SettingUnknown { }
} }
} }
rightButtons: Button rightButtons: Button
{ {
text: catalog.i18nc("@action:button", "Close") text: catalog.i18nc("@action:button", "Close")
@ -466,43 +484,15 @@ UM.Dialog
onClicked: dialog.accept() onClicked: dialog.accept()
} }
Button { Cura.SecondaryButton
{
objectName: "postProcessingSaveAreaButton" objectName: "postProcessingSaveAreaButton"
visible: activeScriptsList.count > 0 visible: activeScriptsList.count > 0
height: UM.Theme.getSize("save_button_save_to_button").height height: UM.Theme.getSize("save_button_save_to_button").height
width: height width: height
tooltip: catalog.i18nc("@info:tooltip", "Change active post-processing scripts") tooltip: catalog.i18nc("@info:tooltip", "Change active post-processing scripts")
onClicked: dialog.show() onClicked: dialog.show()
iconSource: "postprocessing.svg"
style: ButtonStyle { fixedWidthMode: true
background: Rectangle {
id: deviceSelectionIcon
border.width: UM.Theme.getSize("default_lining").width
border.color: !control.enabled ? UM.Theme.getColor("action_button_disabled_border") :
control.pressed ? UM.Theme.getColor("action_button_active_border") :
control.hovered ? UM.Theme.getColor("action_button_hovered_border") : UM.Theme.getColor("action_button_border")
color: !control.enabled ? UM.Theme.getColor("action_button_disabled") :
control.pressed ? UM.Theme.getColor("action_button_active") :
control.hovered ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("action_button")
Behavior on color { ColorAnimation { duration: 50; } }
anchors.left: parent.left
anchors.leftMargin: Math.round(UM.Theme.getSize("save_button_text_margin").width / 2);
width: parent.height
height: parent.height
UM.RecolorImage {
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
width: Math.round(parent.width / 2)
height: Math.round(parent.height / 2)
sourceSize.height: height
color: !control.enabled ? UM.Theme.getColor("action_button_disabled_text") :
control.pressed ? UM.Theme.getColor("action_button_active_text") :
control.hovered ? UM.Theme.getColor("action_button_hovered_text") : UM.Theme.getColor("action_button_text");
source: "postprocessing.svg"
}
}
label: Label{ }
}
} }
} }

View File

@ -163,9 +163,9 @@ Item
id: rangleHandleLabel id: rangleHandleLabel
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
x: parent.x + parent.width + UM.Theme.getSize("default_margin").width x: parent.x - width - UM.Theme.getSize("default_margin").width
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
target: Qt.point(sliderRoot.width + width, y + height / 2) target: Qt.point(sliderRoot.width, y + height / 2)
visible: sliderRoot.activeHandle == parent visible: sliderRoot.activeHandle == parent
// custom properties // custom properties

View File

@ -342,12 +342,16 @@ class SimulationView(CuraView):
return self._extruder_count return self._extruder_count
def getMinFeedrate(self) -> float: def getMinFeedrate(self) -> float:
if abs(self._min_feedrate - sys.float_info.max) < 10: # Some lenience due to floating point rounding.
return 0.0 # If it's still max-float, there are no measurements. Use 0 then.
return self._min_feedrate return self._min_feedrate
def getMaxFeedrate(self) -> float: def getMaxFeedrate(self) -> float:
return self._max_feedrate return self._max_feedrate
def getMinThickness(self) -> float: def getMinThickness(self) -> float:
if abs(self._min_thickness - sys.float_info.max) < 10: # Some lenience due to floating point rounding.
return 0.0 # If it's still max-float, there are no measurements. Use 0 then.
return self._min_thickness return self._min_thickness
def getMaxThickness(self) -> float: def getMaxThickness(self) -> float:

View File

@ -43,7 +43,7 @@ Cura.ExpandableComponent
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
height: parent.height height: parent.height
elide: Text.ElideRight elide: Text.ElideRight
font: UM.Theme.getFont("default") font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("text_medium") color: UM.Theme.getColor("text_medium")
renderType: Text.NativeRendering renderType: Text.NativeRendering
} }
@ -60,7 +60,7 @@ Cura.ExpandableComponent
} }
height: parent.height height: parent.height
elide: Text.ElideRight elide: Text.ElideRight
font: UM.Theme.getFont("default") font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
renderType: Text.NativeRendering renderType: Text.NativeRendering
} }
@ -358,7 +358,7 @@ Cura.ExpandableComponent
width: parent.width width: parent.width
height: UM.Theme.getSize("layerview_row").height height: UM.Theme.getSize("layerview_row").height
Label Label //Minimum value.
{ {
text: text:
{ {
@ -383,9 +383,10 @@ Cura.ExpandableComponent
renderType: Text.NativeRendering renderType: Text.NativeRendering
} }
Label Label //Unit in the middle.
{ {
text: { text:
{
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) if (UM.SimulationView.layerActivity && CuraApplication.platformActivity)
{ {
// Feedrate selected // Feedrate selected
@ -407,7 +408,7 @@ Cura.ExpandableComponent
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
} }
Label Label //Maximum value.
{ {
text: { text: {
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) if (UM.SimulationView.layerActivity && CuraApplication.platformActivity)

View File

@ -30,7 +30,7 @@ Row
width: contentWidth width: contentWidth
anchors.verticalCenter: starIcon.verticalCenter anchors.verticalCenter: starIcon.verticalCenter
color: starIcon.color color: starIcon.color
font: UM.Theme.getFont("small") font: UM.Theme.getFont("default")
renderType: Text.NativeRendering renderType: Text.NativeRendering
} }
} }

View File

@ -55,7 +55,7 @@ Item
bottomMargin: UM.Theme.getSize("default_margin").height bottomMargin: UM.Theme.getSize("default_margin").height
} }
text: details.name || "" text: details.name || ""
font: UM.Theme.getFont("large") font: UM.Theme.getFont("large_bold")
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

View File

@ -61,8 +61,13 @@ Item
id: labelStyle id: labelStyle
text: control.text text: control.text
color: control.enabled ? (control.hovered ? UM.Theme.getColor("primary") : UM.Theme.getColor("text")) : UM.Theme.getColor("text_inactive") color: control.enabled ? (control.hovered ? UM.Theme.getColor("primary") : UM.Theme.getColor("text")) : UM.Theme.getColor("text_inactive")
font: UM.Theme.getFont("default_bold") font: UM.Theme.getFont("medium_bold")
horizontalAlignment: Text.AlignRight horizontalAlignment: Text.AlignLeft
anchors
{
left: parent.left
leftMargin: UM.Theme.getSize("default_margin").width
}
width: control.width width: control.width
renderType: Text.NativeRendering renderType: Text.NativeRendering
} }

View File

@ -59,7 +59,7 @@ Item
leftMargin: UM.Theme.getSize("default_margin").width leftMargin: UM.Theme.getSize("default_margin").width
} }
text: details === null ? "" : (details.name || "") text: details === null ? "" : (details.name || "")
font: UM.Theme.getFont("large") font: UM.Theme.getFont("large_bold")
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
width: contentWidth width: contentWidth
height: contentHeight height: contentHeight

View File

@ -22,7 +22,7 @@ Column
text: gridArea.heading text: gridArea.heading
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("large")
renderType: Text.NativeRendering renderType: Text.NativeRendering
} }
Grid Grid

View File

@ -112,7 +112,7 @@ Item
elide: Text.ElideRight elide: Text.ElideRight
width: parent.width width: parent.width
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
color: UM.Theme.getColor("text_medium") color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
anchors.top: name.bottom anchors.top: name.bottom
anchors.bottom: rating.top anchors.bottom: rating.top

View File

@ -23,7 +23,7 @@ Rectangle
text: catalog.i18nc("@label", "Featured") text: catalog.i18nc("@label", "Featured")
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("large")
renderType: Text.NativeRendering renderType: Text.NativeRendering
} }
Grid Grid

View File

@ -37,7 +37,7 @@ ScrollView
width: page.width width: page.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("large")
renderType: Text.NativeRendering renderType: Text.NativeRendering
} }
Rectangle Rectangle

View File

@ -49,13 +49,14 @@ Item
width: parent.width width: parent.width
height: Math.floor(UM.Theme.getSize("toolbox_property_label").height) height: Math.floor(UM.Theme.getSize("toolbox_property_label").height)
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
font: UM.Theme.getFont("default_bold") font: UM.Theme.getFont("large_bold")
color: pluginInfo.color color: pluginInfo.color
renderType: Text.NativeRendering renderType: Text.NativeRendering
} }
Label Label
{ {
text: model.description text: model.description
font: UM.Theme.getFont("default")
maximumLineCount: 3 maximumLineCount: 3
elide: Text.ElideRight elide: Text.ElideRight
width: parent.width width: parent.width
@ -82,6 +83,7 @@ Item
return model.author_name return model.author_name
} }
} }
font: UM.Theme.getFont("medium")
width: parent.width width: parent.width
height: Math.floor(UM.Theme.getSize("toolbox_property_label").height) height: Math.floor(UM.Theme.getSize("toolbox_property_label").height)
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
@ -96,6 +98,7 @@ Item
Label Label
{ {
text: model.version text: model.version
font: UM.Theme.getFont("default")
width: parent.width width: parent.width
height: UM.Theme.getSize("toolbox_property_label").height height: UM.Theme.getSize("toolbox_property_label").height
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")

View File

@ -28,7 +28,7 @@ Cura.MachineAction
// Check if there is another instance with the same key // Check if there is another instance with the same key
if (!manager.existsKey(printerKey)) if (!manager.existsKey(printerKey))
{ {
manager.setKey(printerKey) manager.associateActiveMachineWithPrinterDevice(base.selectedDevice)
manager.setGroupName(printerName) // TODO To change when the groups have a name manager.setGroupName(printerName) // TODO To change when the groups have a name
completed() completed()
} }
@ -223,7 +223,7 @@ Cura.MachineAction
width: parent.width width: parent.width
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
text: base.selectedDevice ? base.selectedDevice.name : "" text: base.selectedDevice ? base.selectedDevice.name : ""
font: UM.Theme.getFont("large") font: UM.Theme.getFont("large_bold")
elide: Text.ElideRight elide: Text.ElideRight
renderType: Text.NativeRendering renderType: Text.NativeRendering
} }

View File

@ -18,6 +18,10 @@ Item
height: centerSection.height height: centerSection.height
width: maximumWidth width: maximumWidth
// Enable keyboard navigation
Keys.onLeftPressed: navigateTo(currentIndex - 1)
Keys.onRightPressed: navigateTo(currentIndex + 1)
Item Item
{ {
@ -211,7 +215,7 @@ Item
} }
} }
Item Row
{ {
id: navigationDots id: navigationDots
anchors anchors
@ -220,23 +224,20 @@ Item
top: centerSection.bottom top: centerSection.bottom
topMargin: 36 * screenScaleFactor // TODO: Theme! topMargin: 36 * screenScaleFactor // TODO: Theme!
} }
Row spacing: 8 * screenScaleFactor // TODO: Theme!
Repeater
{ {
spacing: 8 * screenScaleFactor // TODO: Theme! model: OutputDevice.printers
Repeater Button
{ {
model: OutputDevice.printers background: Rectangle
Button
{ {
background: Rectangle color: model.index == currentIndex ? "#777777" : "#d8d8d8" // TODO: Theme!
{ radius: Math.floor(width / 2)
color: model.index == currentIndex ? "#777777" : "#d8d8d8" // TODO: Theme! width: 12 * screenScaleFactor // TODO: Theme!
radius: Math.floor(width / 2) height: width // TODO: Theme!
width: 12 * screenScaleFactor // TODO: Theme!
height: width // TODO: Theme!
}
onClicked: navigateTo(model.index)
} }
onClicked: navigateTo(model.index)
} }
} }
} }

View File

@ -81,7 +81,7 @@ Item
text: printer && printer.name ? printer.name : "" text: printer && printer.name ? printer.name : ""
color: "#414054" // TODO: Theme! color: "#414054" // TODO: Theme!
elide: Text.ElideRight elide: Text.ElideRight
font: UM.Theme.getFont("large") // 16pt, bold font: UM.Theme.getFont("large_bold") // 16pt, bold
width: parent.width width: parent.width
// FIXED-LINE-HEIGHT: // FIXED-LINE-HEIGHT:
@ -185,7 +185,7 @@ Item
verticalCenter: parent.verticalCenter verticalCenter: parent.verticalCenter
} }
color: "#414054" // TODO: Theme! color: "#414054" // TODO: Theme!
font: UM.Theme.getFont("large") // 16pt, bold font: UM.Theme.getFont("large_bold") // 16pt, bold
text: { text: {
if (printer && printer.state == "disabled") if (printer && printer.state == "disabled")
{ {
@ -236,7 +236,7 @@ Item
id: printerJobNameLabel id: printerJobNameLabel
color: printer.activePrintJob && printer.activePrintJob.isActive ? "#414054" : "#babac1" // TODO: Theme! color: printer.activePrintJob && printer.activePrintJob.isActive ? "#414054" : "#babac1" // TODO: Theme!
elide: Text.ElideRight elide: Text.ElideRight
font: UM.Theme.getFont("large") // 16pt, bold font: UM.Theme.getFont("large_bold") // 16pt, bold
text: base.printer.activePrintJob ? base.printer.activePrintJob.name : "Untitled" // TODO: I18N text: base.printer.activePrintJob ? base.printer.activePrintJob.name : "Untitled" // TODO: I18N
width: parent.width width: parent.width

View File

@ -23,7 +23,7 @@ Item
top: parent.top top: parent.top
} }
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
font: UM.Theme.getFont("large_nonbold") font: UM.Theme.getFont("large")
text: catalog.i18nc("@label", "Queued") text: catalog.i18nc("@label", "Queued")
} }

View File

@ -24,6 +24,11 @@ Component
} }
} }
width: maximumWidth width: maximumWidth
// Enable keyboard navigation. NOTE: This is done here so that we can also potentially
// forward to the queue items in the future. (Deleting selected print job, etc.)
Keys.forwardTo: carousel
Component.onCompleted: forceActiveFocus()
UM.I18nCatalog UM.I18nCatalog
{ {
@ -59,7 +64,9 @@ Component
} }
width: parent.width width: parent.width
height: 264 * screenScaleFactor // TODO: Theme! height: 264 * screenScaleFactor // TODO: Theme!
MonitorCarousel {} MonitorCarousel {
id: carousel
}
} }
MonitorQueue MonitorQueue

View File

@ -22,6 +22,7 @@ from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationM
from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
from cura.PrinterOutputDevice import ConnectionType
from .ClusterUM3PrinterOutputController import ClusterUM3PrinterOutputController from .ClusterUM3PrinterOutputController import ClusterUM3PrinterOutputController
from .SendMaterialJob import SendMaterialJob from .SendMaterialJob import SendMaterialJob
@ -54,7 +55,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
clusterPrintersChanged = pyqtSignal() clusterPrintersChanged = pyqtSignal()
def __init__(self, device_id, address, properties, parent = None) -> None: def __init__(self, device_id, address, properties, parent = None) -> None:
super().__init__(device_id = device_id, address = address, properties=properties, parent = parent) super().__init__(device_id = device_id, address = address, properties=properties, connection_type = ConnectionType.NetworkConnection, parent = parent)
self._api_prefix = "/cluster-api/v1/" self._api_prefix = "/cluster-api/v1/"
self._number_of_extruders = 2 self._number_of_extruders = 2
@ -619,8 +620,9 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
if material_group_list is None: if material_group_list is None:
material_name = i18n_catalog.i18nc("@label:material", "Empty") if len(material_data.get("guid", "")) == 0 \ material_name = i18n_catalog.i18nc("@label:material", "Empty") if len(material_data.get("guid", "")) == 0 \
else i18n_catalog.i18nc("@label:material", "Unknown") else i18n_catalog.i18nc("@label:material", "Unknown")
return MaterialOutputModel(guid = material_data.get("guid", ""), return MaterialOutputModel(guid = material_data.get("guid", ""),
type = material_data.get("type", ""), type = material_data.get("material", ""),
color = material_data.get("color", ""), color = material_data.get("color", ""),
brand = material_data.get("brand", ""), brand = material_data.get("brand", ""),
name = material_data.get("name", material_name) name = material_data.get("name", material_name)

View File

@ -3,7 +3,7 @@
import os.path import os.path
import time import time
from typing import cast, Optional from typing import Optional, TYPE_CHECKING
from PyQt5.QtCore import pyqtSignal, pyqtProperty, pyqtSlot, QObject from PyQt5.QtCore import pyqtSignal, pyqtProperty, pyqtSlot, QObject
@ -16,6 +16,9 @@ from cura.MachineAction import MachineAction
from .UM3OutputDevicePlugin import UM3OutputDevicePlugin from .UM3OutputDevicePlugin import UM3OutputDevicePlugin
if TYPE_CHECKING:
from cura.PrinterOutputDevice import PrinterOutputDevice
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")
@ -116,22 +119,30 @@ class DiscoverUM3Action(MachineAction):
# Ensure that the connection states are refreshed. # Ensure that the connection states are refreshed.
self._network_plugin.reCheckConnections() self._network_plugin.reCheckConnections()
@pyqtSlot(str) # Associates the currently active machine with the given printer device. The network connection information will be
def setKey(self, key: str) -> None: # stored into the metadata of the currently active machine.
Logger.log("d", "Attempting to set the network key of the active machine to %s", key) @pyqtSlot(QObject)
def associateActiveMachineWithPrinterDevice(self, printer_device: Optional["PrinterOutputDevice"]) -> None:
Logger.log("d", "Attempting to set the network key of the active machine to %s", printer_device.key)
global_container_stack = CuraApplication.getInstance().getGlobalContainerStack() global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
if global_container_stack: if global_container_stack:
meta_data = global_container_stack.getMetaData() meta_data = global_container_stack.getMetaData()
if "um_network_key" in meta_data: if "um_network_key" in meta_data:
previous_network_key= meta_data["um_network_key"] previous_network_key= meta_data["um_network_key"]
global_container_stack.setMetaDataEntry("um_network_key", key) global_container_stack.setMetaDataEntry("um_network_key", printer_device.key)
# Delete old authentication data. # Delete old authentication data.
Logger.log("d", "Removing old authentication id %s for device %s", global_container_stack.getMetaDataEntry("network_authentication_id", None), key) Logger.log("d", "Removing old authentication id %s for device %s", global_container_stack.getMetaDataEntry("network_authentication_id", None), printer_device.key)
global_container_stack.removeMetaDataEntry("network_authentication_id") global_container_stack.removeMetaDataEntry("network_authentication_id")
global_container_stack.removeMetaDataEntry("network_authentication_key") global_container_stack.removeMetaDataEntry("network_authentication_key")
CuraApplication.getInstance().getMachineManager().replaceContainersMetadata(key = "um_network_key", value = previous_network_key, new_value = key) CuraApplication.getInstance().getMachineManager().replaceContainersMetadata(key = "um_network_key", value = previous_network_key, new_value = printer_device.key)
if "connection_type" in meta_data:
previous_connection_type = meta_data["connection_type"]
global_container_stack.setMetaDataEntry("connection_type", printer_device.getConnectionType().value)
CuraApplication.getInstance().getMachineManager().replaceContainersMetadata(key = "connection_type", value = previous_connection_type, new_value = printer_device.getConnectionType().value)
else: else:
global_container_stack.setMetaDataEntry("um_network_key", key) global_container_stack.setMetaDataEntry("um_network_key", printer_device.key)
global_container_stack.setMetaDataEntry("connection_type", printer_device.getConnectionType().value)
if self._network_plugin: if self._network_plugin:
# Ensure that the connection states are refreshed. # Ensure that the connection states are refreshed.

View File

@ -7,6 +7,7 @@ from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutp
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
from cura.PrinterOutputDevice import ConnectionType
from cura.Settings.ContainerManager import ContainerManager from cura.Settings.ContainerManager import ContainerManager
from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderManager import ExtruderManager
@ -43,7 +44,7 @@ i18n_catalog = i18nCatalog("cura")
# 5. As a final step, we verify the authentication, as this forces the QT manager to setup the authenticator. # 5. As a final step, we verify the authentication, as this forces the QT manager to setup the authenticator.
class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice): class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
def __init__(self, device_id, address: str, properties, parent = None) -> None: def __init__(self, device_id, address: str, properties, parent = None) -> None:
super().__init__(device_id = device_id, address = address, properties = properties, parent = parent) super().__init__(device_id = device_id, address = address, properties = properties, connection_type = ConnectionType.NetworkConnection, parent = parent)
self._api_prefix = "/api/v1/" self._api_prefix = "/api/v1/"
self._number_of_extruders = 2 self._number_of_extruders = 2

View File

@ -2,7 +2,6 @@
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
import json import json
import os import os
import urllib.parse
from typing import Dict, TYPE_CHECKING, Set from typing import Dict, TYPE_CHECKING, Set
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
@ -10,9 +9,7 @@ from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
from UM.Application import Application from UM.Application import Application
from UM.Job import Job from UM.Job import Job
from UM.Logger import Logger from UM.Logger import Logger
from UM.MimeTypeDatabase import MimeTypeDatabase
from UM.Resources import Resources
from cura.CuraApplication import CuraApplication
# Absolute imports don't work in plugins # Absolute imports don't work in plugins
from .Models import ClusterMaterial, LocalMaterial from .Models import ClusterMaterial, LocalMaterial
@ -37,7 +34,6 @@ class SendMaterialJob(Job):
# #
# \param reply The reply from the printer, a json file. # \param reply The reply from the printer, a json file.
def _onGetRemoteMaterials(self, reply: QNetworkReply) -> None: def _onGetRemoteMaterials(self, reply: QNetworkReply) -> None:
# Got an error from the HTTP request. If we did not receive a 200 something happened. # Got an error from the HTTP request. If we did not receive a 200 something happened.
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200: if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
Logger.log("e", "Error fetching materials from printer: %s", reply.errorString()) Logger.log("e", "Error fetching materials from printer: %s", reply.errorString())
@ -52,7 +48,6 @@ class SendMaterialJob(Job):
# #
# \param remote_materials_by_guid The remote materials by GUID. # \param remote_materials_by_guid The remote materials by GUID.
def _sendMissingMaterials(self, remote_materials_by_guid: Dict[str, ClusterMaterial]) -> None: def _sendMissingMaterials(self, remote_materials_by_guid: Dict[str, ClusterMaterial]) -> None:
# Collect local materials # Collect local materials
local_materials_by_guid = self._getLocalMaterials() local_materials_by_guid = self._getLocalMaterials()
if len(local_materials_by_guid) == 0: if len(local_materials_by_guid) == 0:
@ -91,22 +86,22 @@ class SendMaterialJob(Job):
# #
# \param materials_to_send A set with id's of materials that must be sent. # \param materials_to_send A set with id's of materials that must be sent.
def _sendMaterials(self, materials_to_send: Set[str]) -> None: def _sendMaterials(self, materials_to_send: Set[str]) -> None:
file_paths = Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.MaterialInstanceContainer) container_registry = Application.getInstance().getContainerRegistry()
material_manager = Application.getInstance().getMaterialManager()
material_group_dict = material_manager.getAllMaterialGroups()
# Find all local material files and send them if needed. for root_material_id in material_group_dict:
for file_path in file_paths: if root_material_id not in materials_to_send:
try:
mime_type = MimeTypeDatabase.getMimeTypeForFile(file_path)
except MimeTypeDatabase.MimeTypeNotFoundError:
continue
file_name = os.path.basename(file_path)
material_id = urllib.parse.unquote_plus(mime_type.stripExtension(file_name))
if material_id not in materials_to_send:
# If the material does not have to be sent we skip it. # If the material does not have to be sent we skip it.
continue continue
self._sendMaterialFile(file_path, file_name, material_id) file_path = container_registry.getContainerFilePathById(root_material_id)
if not file_path:
Logger.log("w", "Cannot get file path for material container [%s]", root_material_id)
continue
file_name = os.path.basename(file_path)
self._sendMaterialFile(file_path, file_name, root_material_id)
## Send a single material file to the printer. ## Send a single material file to the printer.
# #
@ -116,7 +111,6 @@ class SendMaterialJob(Job):
# \param file_name The name of the material file. # \param file_name The name of the material file.
# \param material_id The ID of the material in the file. # \param material_id The ID of the material in the file.
def _sendMaterialFile(self, file_path: str, file_name: str, material_id: str) -> None: def _sendMaterialFile(self, file_path: str, file_name: str, material_id: str) -> None:
parts = [] parts = []
# Add the material file. # Add the material file.
@ -171,27 +165,31 @@ class SendMaterialJob(Job):
# \return a dictionary of LocalMaterial objects by GUID # \return a dictionary of LocalMaterial objects by GUID
def _getLocalMaterials(self) -> Dict[str, LocalMaterial]: def _getLocalMaterials(self) -> Dict[str, LocalMaterial]:
result = {} # type: Dict[str, LocalMaterial] result = {} # type: Dict[str, LocalMaterial]
container_registry = Application.getInstance().getContainerRegistry() material_manager = Application.getInstance().getMaterialManager()
material_containers = container_registry.findContainersMetadata(type = "material")
material_group_dict = material_manager.getAllMaterialGroups()
# Find the latest version of all material containers in the registry. # Find the latest version of all material containers in the registry.
for material in material_containers: for root_material_id, material_group in material_group_dict.items():
material_metadata = material_group.root_material_node.getMetadata()
try: try:
# material version must be an int # material version must be an int
material["version"] = int(material["version"]) material_metadata["version"] = int(material_metadata["version"])
# Create a new local material # Create a new local material
local_material = LocalMaterial(**material) local_material = LocalMaterial(**material_metadata)
local_material.id = root_material_id
if local_material.GUID not in result or \ if local_material.GUID not in result or \
local_material.version > result.get(local_material.GUID).version: local_material.version > result.get(local_material.GUID).version:
result[local_material.GUID] = local_material result[local_material.GUID] = local_material
except KeyError: except KeyError:
Logger.logException("w", "Local material {} has missing values.".format(material["id"])) Logger.logException("w", "Local material {} has missing values.".format(material_metadata["id"]))
except ValueError: except ValueError:
Logger.logException("w", "Local material {} has invalid values.".format(material["id"])) Logger.logException("w", "Local material {} has invalid values.".format(material_metadata["id"]))
except TypeError: except TypeError:
Logger.logException("w", "Local material {} has invalid values.".format(material["id"])) Logger.logException("w", "Local material {} has invalid values.".format(material_metadata["id"]))
return result return result

View File

@ -283,6 +283,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
global_container_stack = Application.getInstance().getGlobalContainerStack() global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack and device.getId() == global_container_stack.getMetaDataEntry("um_network_key"): if global_container_stack and device.getId() == global_container_stack.getMetaDataEntry("um_network_key"):
global_container_stack.setMetaDataEntry("connection_type", device.getConnectionType().value)
device.connect() device.connect()
device.connectionStateChanged.connect(self._onDeviceConnectionStateChanged) device.connectionStateChanged.connect(self._onDeviceConnectionStateChanged)

View File

@ -1,26 +1,29 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
import copy
import io import io
import json import json
from unittest import TestCase, mock from unittest import TestCase, mock
from unittest.mock import patch, call from unittest.mock import patch, call, MagicMock
from PyQt5.QtCore import QByteArray from PyQt5.QtCore import QByteArray
from UM.MimeTypeDatabase import MimeType
from UM.Application import Application from UM.Application import Application
from cura.Machines.MaterialGroup import MaterialGroup
from cura.Machines.MaterialNode import MaterialNode
from plugins.UM3NetworkPrinting.src.SendMaterialJob import SendMaterialJob from plugins.UM3NetworkPrinting.src.SendMaterialJob import SendMaterialJob
_FILES_MAP = {"generic_pla_white": "/materials/generic_pla_white.xml.fdm_material",
"generic_pla_black": "/materials/generic_pla_black.xml.fdm_material",
}
@patch("builtins.open", lambda _, __: io.StringIO("<xml></xml>")) @patch("builtins.open", lambda _, __: io.StringIO("<xml></xml>"))
@patch("UM.MimeTypeDatabase.MimeTypeDatabase.getMimeTypeForFile",
lambda _: MimeType(name = "application/x-ultimaker-material-profile", comment = "Ultimaker Material Profile",
suffixes = ["xml.fdm_material"]))
@patch("UM.Resources.Resources.getAllResourcesOfType", lambda _: ["/materials/generic_pla_white.xml.fdm_material"])
@patch("plugins.UM3NetworkPrinting.src.ClusterUM3OutputDevice")
@patch("PyQt5.QtNetwork.QNetworkReply")
class TestSendMaterialJob(TestCase): class TestSendMaterialJob(TestCase):
# version 1
_LOCAL_MATERIAL_WHITE = {"type": "material", "status": "unknown", "id": "generic_pla_white", _LOCAL_MATERIAL_WHITE = {"type": "material", "status": "unknown", "id": "generic_pla_white",
"base_file": "generic_pla_white", "setting_version": "5", "name": "White PLA", "base_file": "generic_pla_white", "setting_version": "5", "name": "White PLA",
"brand": "Generic", "material": "PLA", "color_name": "White", "brand": "Generic", "material": "PLA", "color_name": "White",
@ -29,6 +32,37 @@ class TestSendMaterialJob(TestCase):
"properties": {"density": "1.00", "diameter": "2.85", "weight": "750"}, "properties": {"density": "1.00", "diameter": "2.85", "weight": "750"},
"definition": "fdmprinter", "compatible": True} "definition": "fdmprinter", "compatible": True}
# version 2
_LOCAL_MATERIAL_WHITE_NEWER = {"type": "material", "status": "unknown", "id": "generic_pla_white",
"base_file": "generic_pla_white", "setting_version": "5", "name": "White PLA",
"brand": "Generic", "material": "PLA", "color_name": "White",
"GUID": "badb0ee7-87c8-4f3f-9398-938587b67dce", "version": "2",
"color_code": "#ffffff",
"description": "Test PLA White", "adhesion_info": "Use glue.",
"approximate_diameter": "3",
"properties": {"density": "1.00", "diameter": "2.85", "weight": "750"},
"definition": "fdmprinter", "compatible": True}
# invalid version: "one"
_LOCAL_MATERIAL_WHITE_INVALID_VERSION = {"type": "material", "status": "unknown", "id": "generic_pla_white",
"base_file": "generic_pla_white", "setting_version": "5", "name": "White PLA",
"brand": "Generic", "material": "PLA", "color_name": "White",
"GUID": "badb0ee7-87c8-4f3f-9398-938587b67dce", "version": "one",
"color_code": "#ffffff",
"description": "Test PLA White", "adhesion_info": "Use glue.",
"approximate_diameter": "3",
"properties": {"density": "1.00", "diameter": "2.85", "weight": "750"},
"definition": "fdmprinter", "compatible": True}
_LOCAL_MATERIAL_WHITE_ALL_RESULT = {"generic_pla_white": MaterialGroup("generic_pla_white",
MaterialNode(_LOCAL_MATERIAL_WHITE))}
_LOCAL_MATERIAL_WHITE_NEWER_ALL_RESULT = {"generic_pla_white": MaterialGroup("generic_pla_white",
MaterialNode(_LOCAL_MATERIAL_WHITE_NEWER))}
_LOCAL_MATERIAL_WHITE_INVALID_VERSION_ALL_RESULT = {"generic_pla_white": MaterialGroup("generic_pla_white",
MaterialNode(_LOCAL_MATERIAL_WHITE_INVALID_VERSION))}
_LOCAL_MATERIAL_BLACK = {"type": "material", "status": "unknown", "id": "generic_pla_black", _LOCAL_MATERIAL_BLACK = {"type": "material", "status": "unknown", "id": "generic_pla_black",
"base_file": "generic_pla_black", "setting_version": "5", "name": "Yellow CPE", "base_file": "generic_pla_black", "setting_version": "5", "name": "Yellow CPE",
"brand": "Ultimaker", "material": "CPE", "color_name": "Black", "brand": "Ultimaker", "material": "CPE", "color_name": "Black",
@ -37,6 +71,9 @@ class TestSendMaterialJob(TestCase):
"properties": {"density": "1.01", "diameter": "2.85", "weight": "750"}, "properties": {"density": "1.01", "diameter": "2.85", "weight": "750"},
"definition": "fdmprinter", "compatible": True} "definition": "fdmprinter", "compatible": True}
_LOCAL_MATERIAL_BLACK_ALL_RESULT = {"generic_pla_black": MaterialGroup("generic_pla_black",
MaterialNode(_LOCAL_MATERIAL_BLACK))}
_REMOTE_MATERIAL_WHITE = { _REMOTE_MATERIAL_WHITE = {
"guid": "badb0ee7-87c8-4f3f-9398-938587b67dce", "guid": "badb0ee7-87c8-4f3f-9398-938587b67dce",
"material": "PLA", "material": "PLA",
@ -55,14 +92,17 @@ class TestSendMaterialJob(TestCase):
"density": 1.00 "density": 1.00
} }
def test_run(self, device_mock, reply_mock): def test_run(self):
device_mock = MagicMock()
job = SendMaterialJob(device_mock) job = SendMaterialJob(device_mock)
job.run() job.run()
# We expect the materials endpoint to be called when the job runs. # We expect the materials endpoint to be called when the job runs.
device_mock.get.assert_called_with("materials/", on_finished = job._onGetRemoteMaterials) device_mock.get.assert_called_with("materials/", on_finished = job._onGetRemoteMaterials)
def test__onGetRemoteMaterials_withFailedRequest(self, reply_mock, device_mock): def test__onGetRemoteMaterials_withFailedRequest(self):
reply_mock = MagicMock()
device_mock = MagicMock()
reply_mock.attribute.return_value = 404 reply_mock.attribute.return_value = 404
job = SendMaterialJob(device_mock) job = SendMaterialJob(device_mock)
job._onGetRemoteMaterials(reply_mock) job._onGetRemoteMaterials(reply_mock)
@ -70,7 +110,9 @@ class TestSendMaterialJob(TestCase):
# We expect the device not to be called for any follow up. # We expect the device not to be called for any follow up.
self.assertEqual(0, device_mock.createFormPart.call_count) self.assertEqual(0, device_mock.createFormPart.call_count)
def test__onGetRemoteMaterials_withWrongEncoding(self, reply_mock, device_mock): def test__onGetRemoteMaterials_withWrongEncoding(self):
reply_mock = MagicMock()
device_mock = MagicMock()
reply_mock.attribute.return_value = 200 reply_mock.attribute.return_value = 200
reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("cp500")) reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("cp500"))
job = SendMaterialJob(device_mock) job = SendMaterialJob(device_mock)
@ -79,7 +121,9 @@ class TestSendMaterialJob(TestCase):
# Given that the parsing fails we do no expect the device to be called for any follow up. # Given that the parsing fails we do no expect the device to be called for any follow up.
self.assertEqual(0, device_mock.createFormPart.call_count) self.assertEqual(0, device_mock.createFormPart.call_count)
def test__onGetRemoteMaterials_withBadJsonAnswer(self, reply_mock, device_mock): def test__onGetRemoteMaterials_withBadJsonAnswer(self):
reply_mock = MagicMock()
device_mock = MagicMock()
reply_mock.attribute.return_value = 200 reply_mock.attribute.return_value = 200
reply_mock.readAll.return_value = QByteArray(b"Six sick hicks nick six slick bricks with picks and sticks.") reply_mock.readAll.return_value = QByteArray(b"Six sick hicks nick six slick bricks with picks and sticks.")
job = SendMaterialJob(device_mock) job = SendMaterialJob(device_mock)
@ -88,7 +132,9 @@ class TestSendMaterialJob(TestCase):
# Given that the parsing fails we do no expect the device to be called for any follow up. # Given that the parsing fails we do no expect the device to be called for any follow up.
self.assertEqual(0, device_mock.createFormPart.call_count) self.assertEqual(0, device_mock.createFormPart.call_count)
def test__onGetRemoteMaterials_withMissingGuidInRemoteMaterial(self, reply_mock, device_mock): def test__onGetRemoteMaterials_withMissingGuidInRemoteMaterial(self):
reply_mock = MagicMock()
device_mock = MagicMock()
reply_mock.attribute.return_value = 200 reply_mock.attribute.return_value = 200
remote_material_without_guid = self._REMOTE_MATERIAL_WHITE.copy() remote_material_without_guid = self._REMOTE_MATERIAL_WHITE.copy()
del remote_material_without_guid["guid"] del remote_material_without_guid["guid"]
@ -99,18 +145,20 @@ class TestSendMaterialJob(TestCase):
# Given that parsing fails we do not expect the device to be called for any follow up. # Given that parsing fails we do not expect the device to be called for any follow up.
self.assertEqual(0, device_mock.createFormPart.call_count) self.assertEqual(0, device_mock.createFormPart.call_count)
@patch("cura.Machines.MaterialManager.MaterialManager")
@patch("cura.Settings.CuraContainerRegistry") @patch("cura.Settings.CuraContainerRegistry")
@patch("UM.Application") @patch("UM.Application")
def test__onGetRemoteMaterials_withInvalidVersionInLocalMaterial(self, application_mock, container_registry_mock, def test__onGetRemoteMaterials_withInvalidVersionInLocalMaterial(self, application_mock, container_registry_mock,
reply_mock, device_mock): material_manager_mock):
reply_mock = MagicMock()
device_mock = MagicMock()
application_mock.getContainerRegistry.return_value = container_registry_mock
application_mock.getMaterialManager.return_value = material_manager_mock
reply_mock.attribute.return_value = 200 reply_mock.attribute.return_value = 200
reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("ascii")) reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("ascii"))
localMaterialWhiteWithInvalidVersion = self._LOCAL_MATERIAL_WHITE.copy() material_manager_mock.getAllMaterialGroups.return_value = self._LOCAL_MATERIAL_WHITE_INVALID_VERSION_ALL_RESULT.copy()
localMaterialWhiteWithInvalidVersion["version"] = "one"
container_registry_mock.findContainersMetadata.return_value = [localMaterialWhiteWithInvalidVersion]
application_mock.getContainerRegistry.return_value = container_registry_mock
with mock.patch.object(Application, "getInstance", new = lambda: application_mock): with mock.patch.object(Application, "getInstance", new = lambda: application_mock):
job = SendMaterialJob(device_mock) job = SendMaterialJob(device_mock)
@ -118,15 +166,16 @@ class TestSendMaterialJob(TestCase):
self.assertEqual(0, device_mock.createFormPart.call_count) self.assertEqual(0, device_mock.createFormPart.call_count)
@patch("cura.Settings.CuraContainerRegistry") @patch("UM.Application.Application.getInstance")
@patch("UM.Application") def test__onGetRemoteMaterials_withNoUpdate(self, application_mock):
def test__onGetRemoteMaterials_withNoUpdate(self, application_mock, container_registry_mock, reply_mock, reply_mock = MagicMock()
device_mock): device_mock = MagicMock()
application_mock.getContainerRegistry.return_value = container_registry_mock container_registry_mock = application_mock.getContainerRegistry.return_value
material_manager_mock = application_mock.getMaterialManager.return_value
device_mock.createFormPart.return_value = "_xXx_" device_mock.createFormPart.return_value = "_xXx_"
container_registry_mock.findContainersMetadata.return_value = [self._LOCAL_MATERIAL_WHITE] material_manager_mock.getAllMaterialGroups.return_value = self._LOCAL_MATERIAL_WHITE_ALL_RESULT.copy()
reply_mock.attribute.return_value = 200 reply_mock.attribute.return_value = 200
reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("ascii")) reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("ascii"))
@ -138,24 +187,25 @@ class TestSendMaterialJob(TestCase):
self.assertEqual(0, device_mock.createFormPart.call_count) self.assertEqual(0, device_mock.createFormPart.call_count)
self.assertEqual(0, device_mock.postFormWithParts.call_count) self.assertEqual(0, device_mock.postFormWithParts.call_count)
@patch("cura.Settings.CuraContainerRegistry") @patch("UM.Application.Application.getInstance")
@patch("UM.Application") def test__onGetRemoteMaterials_withUpdatedMaterial(self, get_instance_mock):
def test__onGetRemoteMaterials_withUpdatedMaterial(self, application_mock, container_registry_mock, reply_mock, reply_mock = MagicMock()
device_mock): device_mock = MagicMock()
application_mock.getContainerRegistry.return_value = container_registry_mock application_mock = get_instance_mock.return_value
container_registry_mock = application_mock.getContainerRegistry.return_value
material_manager_mock = application_mock.getMaterialManager.return_value
container_registry_mock.getContainerFilePathById = lambda x: _FILES_MAP.get(x)
device_mock.createFormPart.return_value = "_xXx_" device_mock.createFormPart.return_value = "_xXx_"
localMaterialWhiteWithHigherVersion = self._LOCAL_MATERIAL_WHITE.copy() material_manager_mock.getAllMaterialGroups.return_value = self._LOCAL_MATERIAL_WHITE_NEWER_ALL_RESULT.copy()
localMaterialWhiteWithHigherVersion["version"] = "2"
container_registry_mock.findContainersMetadata.return_value = [localMaterialWhiteWithHigherVersion]
reply_mock.attribute.return_value = 200 reply_mock.attribute.return_value = 200
reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("ascii")) reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("ascii"))
with mock.patch.object(Application, "getInstance", new = lambda: application_mock): job = SendMaterialJob(device_mock)
job = SendMaterialJob(device_mock) job._onGetRemoteMaterials(reply_mock)
job._onGetRemoteMaterials(reply_mock)
self.assertEqual(1, device_mock.createFormPart.call_count) self.assertEqual(1, device_mock.createFormPart.call_count)
self.assertEqual(1, device_mock.postFormWithParts.call_count) self.assertEqual(1, device_mock.postFormWithParts.call_count)
@ -164,16 +214,21 @@ class TestSendMaterialJob(TestCase):
call.postFormWithParts(target = "materials/", parts = ["_xXx_"], on_finished = job.sendingFinished)], call.postFormWithParts(target = "materials/", parts = ["_xXx_"], on_finished = job.sendingFinished)],
device_mock.method_calls) device_mock.method_calls)
@patch("cura.Settings.CuraContainerRegistry") @patch("UM.Application.Application.getInstance")
@patch("UM.Application") def test__onGetRemoteMaterials_withNewMaterial(self, application_mock):
def test__onGetRemoteMaterials_withNewMaterial(self, application_mock, container_registry_mock, reply_mock, reply_mock = MagicMock()
device_mock): device_mock = MagicMock()
application_mock.getContainerRegistry.return_value = container_registry_mock container_registry_mock = application_mock.getContainerRegistry.return_value
material_manager_mock = application_mock.getMaterialManager.return_value
container_registry_mock.getContainerFilePathById = lambda x: _FILES_MAP.get(x)
device_mock.createFormPart.return_value = "_xXx_" device_mock.createFormPart.return_value = "_xXx_"
container_registry_mock.findContainersMetadata.return_value = [self._LOCAL_MATERIAL_WHITE, all_results = self._LOCAL_MATERIAL_WHITE_ALL_RESULT.copy()
self._LOCAL_MATERIAL_BLACK] for key, value in self._LOCAL_MATERIAL_BLACK_ALL_RESULT.items():
all_results[key] = value
material_manager_mock.getAllMaterialGroups.return_value = all_results
reply_mock.attribute.return_value = 200 reply_mock.attribute.return_value = 200
reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_BLACK]).encode("ascii")) reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_BLACK]).encode("ascii"))

View File

@ -13,6 +13,8 @@ Component
{ {
Rectangle Rectangle
{ {
color: UM.Theme.getColor("main_background")
anchors.right: parent.right anchors.right: parent.right
width: parent.width * 0.3 width: parent.width * 0.3
anchors.top: parent.top anchors.top: parent.top

View File

@ -7,7 +7,7 @@ from UM.i18n import i18nCatalog
from UM.Qt.Duration import DurationFormat from UM.Qt.Duration import DurationFormat
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState, ConnectionType
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
from cura.PrinterOutput.GenericOutputController import GenericOutputController from cura.PrinterOutput.GenericOutputController import GenericOutputController
@ -29,7 +29,7 @@ catalog = i18nCatalog("cura")
class USBPrinterOutputDevice(PrinterOutputDevice): class USBPrinterOutputDevice(PrinterOutputDevice):
def __init__(self, serial_port: str, baud_rate: Optional[int] = None) -> None: def __init__(self, serial_port: str, baud_rate: Optional[int] = None) -> None:
super().__init__(serial_port) super().__init__(serial_port, connection_type = ConnectionType.UsbConnection)
self.setName(catalog.i18nc("@item:inmenu", "USB printing")) self.setName(catalog.i18nc("@item:inmenu", "USB printing"))
self.setShortDescription(catalog.i18nc("@action:button Preceded by 'Ready to'.", "Print via USB")) self.setShortDescription(catalog.i18nc("@action:button Preceded by 'Ready to'.", "Print via USB"))
self.setDescription(catalog.i18nc("@info:tooltip", "Print via USB")) self.setDescription(catalog.i18nc("@info:tooltip", "Print via USB"))
@ -179,7 +179,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
return return
CuraApplication.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerStackChanged) CuraApplication.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerStackChanged)
self._onGlobalContainerStackChanged() self._onGlobalContainerStackChanged()
self.setConnectionState(ConnectionState.connected) self.setConnectionState(ConnectionState.Connected)
self._update_thread.start() self._update_thread.start()
def _onGlobalContainerStackChanged(self): def _onGlobalContainerStackChanged(self):
@ -208,7 +208,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
self._sendCommand(command) self._sendCommand(command)
def _sendCommand(self, command: Union[str, bytes]): def _sendCommand(self, command: Union[str, bytes]):
if self._serial is None or self._connection_state != ConnectionState.connected: if self._serial is None or self._connection_state != ConnectionState.Connected:
return return
new_command = cast(bytes, command) if type(command) is bytes else cast(str, command).encode() # type: bytes new_command = cast(bytes, command) if type(command) is bytes else cast(str, command).encode() # type: bytes
@ -222,7 +222,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
self._command_received.set() self._command_received.set()
def _update(self): def _update(self):
while self._connection_state == ConnectionState.connected and self._serial is not None: while self._connection_state == ConnectionState.Connected and self._serial is not None:
try: try:
line = self._serial.readline() line = self._serial.readline()
except: except:

View File

@ -66,7 +66,7 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
return return
changed_device = self._usb_output_devices[serial_port] changed_device = self._usb_output_devices[serial_port]
if changed_device.connectionState == ConnectionState.connected: if changed_device.connectionState == ConnectionState.Connected:
self.getOutputDeviceManager().addOutputDevice(changed_device) self.getOutputDeviceManager().addOutputDevice(changed_device)
else: else:
self.getOutputDeviceManager().removeOutputDevice(serial_port) self.getOutputDeviceManager().removeOutputDevice(serial_port)

View File

@ -47,7 +47,7 @@ def getMetaData() -> Dict[str, Any]:
}, },
"user": { "user": {
"get_version": upgrade.getCfgVersion, "get_version": upgrade.getCfgVersion,
"location": {"./user"} "location": {"./user", "./materials/*"}
}, },
"variant": { "variant": {
"get_version": upgrade.getCfgVersion, "get_version": upgrade.getCfgVersion,

View File

@ -57,7 +57,7 @@
"display_name": "Cura Backups", "display_name": "Cura Backups",
"description": "Backup and restore your configuration.", "description": "Backup and restore your configuration.",
"package_version": "1.2.0", "package_version": "1.2.0",
"sdk_version": 5, "sdk_version": 6,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "UltimakerPackages", "author_id": "UltimakerPackages",

View File

@ -16,7 +16,8 @@
{ {
"0": "ultimaker_original_plus_extruder_0" "0": "ultimaker_original_plus_extruder_0"
}, },
"firmware_file": "MarlinUltimaker-UMOP-{baudrate}.hex" "firmware_file": "MarlinUltimaker-UMOP-{baudrate}.hex",
"firmware_hbk_file": "MarlinUltimaker-UMOP-{baudrate}.hex"
}, },
"overrides": { "overrides": {

View File

@ -44,7 +44,7 @@ Column
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
renderType: Text.NativeRendering renderType: Text.NativeRendering
text: loggedIn ? profile["username"] : catalog.i18nc("@label", "Please log in or create an account to\nenjoy all features of Ultimaker Cura.") text: loggedIn ? profile["username"] : catalog.i18nc("@label", "Please log in or create an account to\nenjoy all features of Ultimaker Cura.")
font: loggedIn ? UM.Theme.getFont("large") : UM.Theme.getFont("default") font: loggedIn ? UM.Theme.getFont("large_bold") : UM.Theme.getFont("default")
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
} }

View File

@ -17,7 +17,7 @@ Button
property alias iconSource: buttonIconLeft.source property alias iconSource: buttonIconLeft.source
property alias textFont: buttonText.font property alias textFont: buttonText.font
property alias cornerRadius: backgroundRect.radius property alias cornerRadius: backgroundRect.radius
property alias tooltip: tooltip.text property alias tooltip: tooltip.tooltipText
property alias cornerSide: backgroundRect.cornerSide property alias cornerSide: backgroundRect.cornerSide
property color color: UM.Theme.getColor("primary") property color color: UM.Theme.getColor("primary")
@ -33,6 +33,8 @@ Button
property alias shadowEnabled: shadow.visible property alias shadowEnabled: shadow.visible
property alias busy: busyIndicator.visible property alias busy: busyIndicator.visible
property alias toolTipContentAlignment: tooltip.contentAlignment
// This property is used to indicate whether the button has a fixed width or the width would depend on the contents // This property is used to indicate whether the button has a fixed width or the width would depend on the contents
// Be careful when using fixedWidthMode, the translated texts can be too long that they won't fit. In any case, // Be careful when using fixedWidthMode, the translated texts can be too long that they won't fit. In any case,
// we elide the text to the right so the text will be cut off with the three dots at the end. // we elide the text to the right so the text will be cut off with the three dots at the end.
@ -112,20 +114,17 @@ Button
z: backgroundRect.z - 1 z: backgroundRect.z - 1
} }
ToolTip Cura.ToolTip
{ {
id: tooltip id: tooltip
text: "" visible: button.hovered
delay: 500
visible: text != "" && button.hovered
} }
BusyIndicator { BusyIndicator
{
id: busyIndicator id: busyIndicator
anchors { anchors.centerIn: parent
centerIn: parent
}
width: height width: height
height: parent.height height: parent.height

View File

@ -23,6 +23,7 @@ Rectangle
border.width: UM.Theme.getSize("default_lining").width border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining") border.color: UM.Theme.getColor("lining")
radius: UM.Theme.getSize("default_radius").width radius: UM.Theme.getSize("default_radius").width
z: 10
property bool outputAvailable: UM.Backend.state == UM.Backend.Done || UM.Backend.state == UM.Backend.Disabled property bool outputAvailable: UM.Backend.state == UM.Backend.Done || UM.Backend.state == UM.Backend.Disabled

View File

@ -60,7 +60,6 @@ Item
leftPadding: UM.Theme.getSize("narrow_margin").width //Need more space than usual here for wide text. leftPadding: UM.Theme.getSize("narrow_margin").width //Need more space than usual here for wide text.
rightPadding: UM.Theme.getSize("narrow_margin").width rightPadding: UM.Theme.getSize("narrow_margin").width
tooltip: popup.opened ? "" : catalog.i18nc("@info:tooltip", "Select the active output device")
iconSource: popup.opened ? UM.Theme.getIcon("arrow_top") : UM.Theme.getIcon("arrow_bottom") iconSource: popup.opened ? UM.Theme.getIcon("arrow_top") : UM.Theme.getIcon("arrow_bottom")
color: UM.Theme.getColor("action_panel_secondary") color: UM.Theme.getColor("action_panel_secondary")
visible: (devicesModel.deviceCount > 1) visible: (devicesModel.deviceCount > 1)
@ -94,7 +93,6 @@ Item
onClicked: onClicked:
{ {
UM.OutputDeviceManager.setActiveDevice(model.id) UM.OutputDeviceManager.setActiveDevice(model.id)
widget.requestWriteToDevice()
popup.close() popup.close()
} }
} }

View File

@ -40,8 +40,7 @@ Column
anchors anchors
{ {
left: parent.left left: parent.left
right: printInformationPanel.left right: parent.right
rightMargin: printInformationPanel.visible ? UM.Theme.getSize("thin_margin").width : 0
} }
Cura.IconWithText Cura.IconWithText
@ -51,7 +50,15 @@ Column
text: preSlicedData ? catalog.i18nc("@label", "No time estimation available") : PrintInformation.currentPrintTime.getDisplayString(UM.DurationFormat.Long) text: preSlicedData ? catalog.i18nc("@label", "No time estimation available") : PrintInformation.currentPrintTime.getDisplayString(UM.DurationFormat.Long)
source: UM.Theme.getIcon("clock") source: UM.Theme.getIcon("clock")
font: UM.Theme.getFont("default_bold") font: UM.Theme.getFont("large_bold")
PrintInformationWidget
{
id: printInformationPanel
visible: !preSlicedData
anchors.left: parent.left
anchors.leftMargin: parent.contentWidth + UM.Theme.getSize("default_margin").width
}
} }
Cura.IconWithText Cura.IconWithText
@ -84,20 +91,43 @@ Column
return totalWeights + "g · " + totalLengths.toFixed(2) + "m" return totalWeights + "g · " + totalLengths.toFixed(2) + "m"
} }
source: UM.Theme.getIcon("spool") source: UM.Theme.getIcon("spool")
Item
{
id: additionalComponents
width: childrenRect.width
anchors.right: parent.right
height: parent.height
Row
{
id: additionalComponentsRow
anchors.right: parent.right
anchors.bottom: parent.bottom
spacing: UM.Theme.getSize("default_margin").width
}
}
Component.onCompleted: addAdditionalComponents("saveButton")
Connections
{
target: CuraApplication
onAdditionalComponentsChanged: addAdditionalComponents("saveButton")
}
function addAdditionalComponents (areaId)
{
if(areaId == "saveButton")
{
for (var component in CuraApplication.additionalComponents["saveButton"])
{
CuraApplication.additionalComponents["saveButton"][component].parent = additionalComponentsRow
}
}
}
} }
} }
PrintInformationWidget
{
id: printInformationPanel
visible: !preSlicedData
anchors
{
right: parent.right
verticalCenter: timeAndCostsInformation.verticalCenter
}
}
} }
Item Item
@ -123,11 +153,10 @@ Column
tooltip: text tooltip: text
fixedWidthMode: true fixedWidthMode: true
toolTipContentAlignment: Cura.ToolTip.ContentAlignment.AlignLeft
onClicked: UM.Controller.setActiveStage("PreviewStage") onClicked: UM.Controller.setActiveStage("PreviewStage")
visible: UM.Controller.activeStage != null && UM.Controller.activeStage.stageId != "PreviewStage" visible: UM.Controller.activeStage != null && UM.Controller.activeStage.stageId != "PreviewStage"
shadowEnabled: true
shadowColor: UM.Theme.getColor("action_button_disabled_shadow")
} }
Cura.OutputDevicesActionButton Cura.OutputDevicesActionButton

View File

@ -44,9 +44,9 @@ Column
{ {
id: autoSlicingLabel id: autoSlicingLabel
width: parent.width width: parent.width
visible: prepareButtons.autoSlice && (widget.backendState == UM.Backend.Processing || widget.backendState == UM.Backend.NotStarted) visible: progressBar.visible
text: catalog.i18nc("@label:PrintjobStatus", "Auto slicing...") text: catalog.i18nc("@label:PrintjobStatus", "Slicing...")
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
renderType: Text.NativeRendering renderType: Text.NativeRendering
@ -107,8 +107,15 @@ Column
{ {
id: sliceButton id: sliceButton
fixedWidthMode: true fixedWidthMode: true
anchors.fill: parent
height: parent.height
anchors.right: additionalComponents.left
anchors.rightMargin: additionalComponents.width != 0 ? UM.Theme.getSize("default_margin").width : 0
anchors.left: parent.left
text: catalog.i18nc("@button", "Slice") text: catalog.i18nc("@button", "Slice")
tooltip: catalog.i18nc("@label", "Start the slicing process")
enabled: widget.backendState != UM.Backend.Error enabled: widget.backendState != UM.Backend.Error
visible: widget.backendState == UM.Backend.NotStarted || widget.backendState == UM.Backend.Error visible: widget.backendState == UM.Backend.NotStarted || widget.backendState == UM.Backend.Error
onClicked: sliceOrStopSlicing() onClicked: sliceOrStopSlicing()
@ -118,12 +125,48 @@ Column
{ {
id: cancelButton id: cancelButton
fixedWidthMode: true fixedWidthMode: true
anchors.fill: parent height: parent.height
anchors.left: parent.left
anchors.right: additionalComponents.left
anchors.rightMargin: additionalComponents.width != 0 ? UM.Theme.getSize("default_margin").width : 0
text: catalog.i18nc("@button", "Cancel") text: catalog.i18nc("@button", "Cancel")
enabled: sliceButton.enabled enabled: sliceButton.enabled
visible: !sliceButton.visible visible: !sliceButton.visible
onClicked: sliceOrStopSlicing() onClicked: sliceOrStopSlicing()
} }
Item
{
id: additionalComponents
width: childrenRect.width
anchors.right: parent.right
height: parent.height
Row
{
id: additionalComponentsRow
anchors.verticalCenter: parent.verticalCenter
spacing: UM.Theme.getSize("default_margin").width
}
}
Component.onCompleted: prepareButtons.addAdditionalComponents("saveButton")
Connections
{
target: CuraApplication
onAdditionalComponentsChanged: prepareButtons.addAdditionalComponents("saveButton")
}
function addAdditionalComponents (areaId)
{
if(areaId == "saveButton")
{
for (var component in CuraApplication.additionalComponents["saveButton"])
{
CuraApplication.additionalComponents["saveButton"][component].parent = additionalComponentsRow
}
}
}
} }
@ -134,10 +177,13 @@ Column
onPreferenceChanged: onPreferenceChanged:
{ {
var autoSlice = UM.Preferences.getValue("general/auto_slice") var autoSlice = UM.Preferences.getValue("general/auto_slice")
prepareButtons.autoSlice = autoSlice if(prepareButtons.autoSlice != autoSlice)
if(autoSlice)
{ {
CuraApplication.backend.forceSlice() prepareButtons.autoSlice = autoSlice
if(autoSlice)
{
CuraApplication.backend.forceSlice()
}
} }
} }
} }

View File

@ -13,11 +13,12 @@ CheckBox
property alias tooltip: tooltip.text property alias tooltip: tooltip.text
indicator: Rectangle { indicator: Rectangle
{
implicitWidth: UM.Theme.getSize("checkbox").width implicitWidth: UM.Theme.getSize("checkbox").width
implicitHeight: UM.Theme.getSize("checkbox").height implicitHeight: UM.Theme.getSize("checkbox").height
x: 0 x: 0
y: Math.round(parent.height / 2 - height / 2) anchors.verticalCenter: parent.verticalCenter
color: UM.Theme.getColor("main_background") color: UM.Theme.getColor("main_background")
radius: UM.Theme.getSize("checkbox_radius").width radius: UM.Theme.getSize("checkbox_radius").width
border.width: UM.Theme.getSize("default_lining").width border.width: UM.Theme.getSize("default_lining").width
@ -37,7 +38,8 @@ CheckBox
} }
} }
contentItem: Label { contentItem: Label
{
anchors anchors
{ {
left: checkbox.indicator.right left: checkbox.indicator.right

View File

@ -51,7 +51,7 @@ UM.Dialog
id: version id: version
text: catalog.i18nc("@label","version: %1").arg(UM.Application.version) text: catalog.i18nc("@label","version: %1").arg(UM.Application.version)
font: UM.Theme.getFont("large") font: UM.Theme.getFont("large_bold")
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
anchors.right : logo.right anchors.right : logo.right
anchors.top: logo.bottom anchors.top: logo.bottom

View File

@ -64,7 +64,7 @@ Item
property alias iconSize: collapseButton.height property alias iconSize: collapseButton.height
// Is the "drawer" open? // Is the "drawer" open?
readonly property alias expanded: contentContainer.visible property alias expanded: contentContainer.visible
// What should the radius of the header be. This is also influenced by the headerCornerSide // What should the radius of the header be. This is also influenced by the headerCornerSide
property alias headerRadius: background.radius property alias headerRadius: background.radius

View File

@ -25,7 +25,7 @@ Cura.RoundedRectangle
{ {
id: headerLabel id: headerLabel
text: "" text: ""
font: UM.Theme.getFont("default") font: UM.Theme.getFont("medium")
renderType: Text.NativeRendering renderType: Text.NativeRendering
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
color: UM.Theme.getColor("small_button_text") color: UM.Theme.getColor("small_button_text")

View File

@ -48,7 +48,7 @@ Item
id: extruderNumberText id: extruderNumberText
anchors.centerIn: parent anchors.centerIn: parent
text: index + 1 text: index + 1
font: UM.Theme.getFont("very_small") font: UM.Theme.getFont("small")
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
width: contentWidth width: contentWidth
height: contentHeight height: contentHeight
@ -66,7 +66,7 @@ Item
sourceSize.height: width sourceSize.height: width
source: UM.Theme.getIcon("cross1") source: UM.Theme.getIcon("cross1")
visible: !extruderEnabled visible: !extruderEnabled
color: "black" color: UM.Theme.getColor("text")
} }
} }
} }

View File

@ -97,7 +97,7 @@ Item
style: TextFieldStyle style: TextFieldStyle
{ {
textColor: UM.Theme.getColor("text_scene") textColor: UM.Theme.getColor("text_scene")
font: UM.Theme.getFont("default_bold") font: UM.Theme.getFont("default")
background: Rectangle background: Rectangle
{ {
opacity: 0 opacity: 0
@ -115,7 +115,7 @@ Item
height: UM.Theme.getSize("jobspecs_line").height height: UM.Theme.getSize("jobspecs_line").height
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
font: UM.Theme.getFont("default_bold") font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text_scene") color: UM.Theme.getColor("text_scene")
text: CuraApplication.getSceneBoundingBoxString text: CuraApplication.getSceneBoundingBoxString
} }

View File

@ -83,14 +83,6 @@ Item
} }
} }
Menu
{
id: plugin_menu
title: catalog.i18nc("@title:menu menubar:toplevel", "&Marketplace")
MenuItem { action: Cura.Actions.browsePackages }
}
Menu Menu
{ {
id: preferencesMenu id: preferencesMenu

View File

@ -29,6 +29,9 @@ Item
source: UM.Theme.getImage("logo") source: UM.Theme.getImage("logo")
width: UM.Theme.getSize("logo").width width: UM.Theme.getSize("logo").width
height: UM.Theme.getSize("logo").height height: UM.Theme.getSize("logo").height
sourceSize.width: width
sourceSize.height: height
} }
Row Row
@ -99,6 +102,7 @@ Item
{ {
id: label id: label
text: marketplaceButton.text text: marketplaceButton.text
font: UM.Theme.getFont("default")
color: marketplaceButton.hovered ? UM.Theme.getColor("main_window_header_background") : UM.Theme.getColor("primary_text") color: marketplaceButton.hovered ? UM.Theme.getColor("main_window_header_background") : UM.Theme.getColor("primary_text")
width: contentWidth width: contentWidth
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter

View File

@ -16,7 +16,7 @@ Item
{ {
id: header id: header
text: catalog.i18nc("@header", "Configurations") text: catalog.i18nc("@header", "Configurations")
font: UM.Theme.getFont("default") font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("small_button_text") color: UM.Theme.getColor("small_button_text")
height: contentHeight height: contentHeight
renderType: Text.NativeRendering renderType: Text.NativeRendering

View File

@ -12,7 +12,23 @@ Button
id: configurationItem id: configurationItem
property var configuration: null property var configuration: null
hoverEnabled: true hoverEnabled: isValidMaterial
property bool isValidMaterial:
{
var extruderConfigurations = configuration.extruderConfigurations
for (var index in extruderConfigurations)
{
var name = extruderConfigurations[index].material ? extruderConfigurations[index].material.name : ""
if (name == "" || name == "Unknown")
{
return false
}
}
return true
}
background: Rectangle background: Rectangle
{ {
@ -40,17 +56,104 @@ Button
right: parent.right right: parent.right
rightMargin: UM.Theme.getSize("wide_margin").width rightMargin: UM.Theme.getSize("wide_margin").width
} }
height: childrenRect.height
spacing: UM.Theme.getSize("default_margin").width spacing: UM.Theme.getSize("default_margin").width
Repeater Repeater
{ {
id: repeater id: repeater
model: configuration.extruderConfigurations model: configuration.extruderConfigurations
delegate: PrintCoreConfiguration delegate: PrintCoreConfiguration
{ {
width: Math.round(parent.width / 2) width: Math.round(parent.width / 2)
printCoreConfiguration: modelData printCoreConfiguration: modelData
visible: configurationItem.isValidMaterial
}
}
// Unknown material
Item
{
id: unknownMaterial
height: unknownMaterialMessage.height + UM.Theme.getSize("thin_margin").width / 2
width: parent.width
anchors.top: parent.top
anchors.topMargin: UM.Theme.getSize("thin_margin").width / 2
visible: !configurationItem.isValidMaterial
UM.RecolorImage
{
id: icon
anchors.verticalCenter: unknownMaterialMessage.verticalCenter
source: UM.Theme.getIcon("warning")
color: UM.Theme.getColor("warning")
width: UM.Theme.getSize("section_icon").width
height: width
}
Label
{
id: unknownMaterialMessage
text:
{
var extruderConfigurations = configuration.extruderConfigurations
var unknownMaterials = []
for (var index in extruderConfigurations)
{
var name = extruderConfigurations[index].material ? extruderConfigurations[index].material.name : ""
if (name == "" || name == "Unknown")
{
var materialType = extruderConfigurations[index].material.type
if (extruderConfigurations[index].material.type == "")
{
materialType = "Unknown"
}
var brand = extruderConfigurations[index].material.brand
if (brand == "")
{
brand = "Unknown"
}
name = materialType + " (" + brand + ")"
unknownMaterials.push(name)
}
}
unknownMaterials = "<b>" + unknownMaterials + "</b>"
var draftResult = catalog.i18nc("@label", "This configuration is not available because %1 is not recognized. Please visit %2 to download the correct material profile.");
var result = draftResult.arg(unknownMaterials).arg("<a href=' '>" + catalog.i18nc("@label","Marketplace") + "</a> ")
return result
}
width: extruderRow.width
anchors.left: icon.right
anchors.right: unknownMaterial.right
anchors.leftMargin: UM.Theme.getSize("wide_margin").height
anchors.top: unknownMaterial.top
wrapMode: Text.WordWrap
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
verticalAlignment: Text.AlignVCenter
linkColor: UM.Theme.getColor("text_link")
onLinkActivated:
{
Cura.Actions.browsePackages.trigger()
}
}
MouseArea
{
anchors.fill: parent
cursorShape: unknownMaterialMessage.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
acceptedButtons: Qt.NoButton
} }
} }
} }
@ -84,27 +187,15 @@ Button
rightMargin: UM.Theme.getSize("wide_margin").width rightMargin: UM.Theme.getSize("wide_margin").width
} }
height: childrenRect.height height: childrenRect.height
visible: configuration.buildplateConfiguration != "" visible: configuration.buildplateConfiguration != "" && false //Buildplate is disabled as long as we have no printers that properly support buildplate swapping (so we can't test).
UM.RecolorImage // Show the type of buildplate. The first letter is capitalized
{ Cura.IconWithText
id: buildplateIcon
anchors.left: parent.left
width: UM.Theme.getSize("main_window_header_button_icon").width
height: UM.Theme.getSize("main_window_header_button_icon").height
source: UM.Theme.getIcon("buildplate")
color: UM.Theme.getColor("text")
}
Label
{ {
id: buildplateLabel id: buildplateLabel
anchors.left: buildplateIcon.right source: UM.Theme.getIcon("buildplate")
anchors.verticalCenter: buildplateIcon.verticalCenter text: configuration.buildplateConfiguration.charAt(0).toUpperCase() + configuration.buildplateConfiguration.substr(1)
anchors.leftMargin: UM.Theme.getSize("narrow_margin").height anchors.left: parent.left
text: configuration.buildplateConfiguration
renderType: Text.NativeRendering
color: UM.Theme.getColor("text")
} }
} }
} }
@ -125,6 +216,9 @@ Button
onClicked: onClicked:
{ {
Cura.MachineManager.applyRemoteConfiguration(configuration) if(isValidMaterial)
{
Cura.MachineManager.applyRemoteConfiguration(configuration);
}
} }
} }

View File

@ -51,7 +51,7 @@ Item
anchors.left: icon.right anchors.left: icon.right
anchors.right: parent.right anchors.right: parent.right
anchors.leftMargin: UM.Theme.getSize("default_margin").width anchors.leftMargin: UM.Theme.getSize("default_margin").width
text: catalog.i18nc("@label", "The configurations are not available because the printer is disconnected.") text: catalog.i18nc("@label", "Downloading the configurations from the remote printer")
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
renderType: Text.NativeRendering renderType: Text.NativeRendering

View File

@ -86,7 +86,7 @@ Cura.ExpandablePopup
{ {
text: model.material text: model.material
elide: Text.ElideRight elide: Text.ElideRight
font: UM.Theme.getFont("default") font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
renderType: Text.NativeRendering renderType: Text.NativeRendering
@ -107,7 +107,7 @@ Cura.ExpandablePopup
{ {
text: catalog.i18nc("@label", "Select configuration") text: catalog.i18nc("@label", "Select configuration")
elide: Text.ElideRight elide: Text.ElideRight
font: UM.Theme.getFont("default") font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
renderType: Text.NativeRendering renderType: Text.NativeRendering
@ -136,7 +136,7 @@ Cura.ExpandablePopup
onVisibleChanged: onVisibleChanged:
{ {
is_connected = Cura.MachineManager.activeMachineNetworkKey !== "" && Cura.MachineManager.printerConnected // Re-evaluate. is_connected = Cura.MachineManager.activeMachineHasRemoteConnection && Cura.MachineManager.printerConnected //Re-evaluate.
// If the printer is not connected, we switch always to the custom mode. If is connected instead, the auto mode // If the printer is not connected, we switch always to the custom mode. If is connected instead, the auto mode
// or the previous state is selected // or the previous state is selected

View File

@ -23,7 +23,7 @@ Item
{ {
id: header id: header
text: catalog.i18nc("@header", "Custom") text: catalog.i18nc("@header", "Custom")
font: UM.Theme.getFont("default") font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("small_button_text") color: UM.Theme.getColor("small_button_text")
height: contentHeight height: contentHeight
renderType: Text.NativeRendering renderType: Text.NativeRendering

View File

@ -39,7 +39,7 @@ Row
text: printCoreConfiguration.material.brand ? printCoreConfiguration.material.name : " " //Use space so that the height is still correct. text: printCoreConfiguration.material.brand ? printCoreConfiguration.material.name : " " //Use space so that the height is still correct.
renderType: Text.NativeRendering renderType: Text.NativeRendering
elide: Text.ElideRight elide: Text.ElideRight
font: UM.Theme.getFont("default") font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
} }
Label Label

View File

@ -7,16 +7,18 @@ import QtQuick.Controls 1.4
import UM 1.2 as UM import UM 1.2 as UM
import Cura 1.0 as Cura import Cura 1.0 as Cura
Instantiator { Instantiator
model: UM.ContainerStacksModel { {
filter: {"type": "machine", "um_network_key": null} model: Cura.GlobalStacksModel {}
}
MenuItem { MenuItem
text: model.name; {
checkable: true; text: model.name
checkable: true
checked: Cura.MachineManager.activeMachineId == model.id checked: Cura.MachineManager.activeMachineId == model.id
exclusiveGroup: group; exclusiveGroup: group
onTriggered: Cura.MachineManager.setActiveMachine(model.id); visible: !model.hasRemoteConnection
onTriggered: Cura.MachineManager.setActiveMachine(model.id)
} }
onObjectAdded: menu.insertItem(index, object) onObjectAdded: menu.insertItem(index, object)
onObjectRemoved: menu.removeItem(object) onObjectRemoved: menu.removeItem(object)

View File

@ -7,18 +7,17 @@ import QtQuick.Controls 1.4
import UM 1.2 as UM import UM 1.2 as UM
import Cura 1.0 as Cura import Cura 1.0 as Cura
Instantiator { Instantiator
model: UM.ContainerStacksModel { {
filter: {"type": "machine", "um_network_key": "*", "hidden": "False"} model: Cura.GlobalStacksModel {}
} MenuItem
MenuItem { {
// TODO: Use printer_group icon when it's a cluster. Not use it for now since it doesn't look as expected
// iconSource: UM.Theme.getIcon("printer_single")
text: model.metadata["connect_group_name"] text: model.metadata["connect_group_name"]
checkable: true; checkable: true
visible: model.hasRemoteConnection
checked: Cura.MachineManager.activeMachineNetworkGroupName == model.metadata["connect_group_name"] checked: Cura.MachineManager.activeMachineNetworkGroupName == model.metadata["connect_group_name"]
exclusiveGroup: group; exclusiveGroup: group
onTriggered: Cura.MachineManager.setActiveMachine(model.id); onTriggered: Cura.MachineManager.setActiveMachine(model.id)
} }
onObjectAdded: menu.insertItem(index, object) onObjectAdded: menu.insertItem(index, object)
onObjectRemoved: menu.removeItem(object) onObjectRemoved: menu.removeItem(object)

View File

@ -168,7 +168,7 @@ Item
anchors.leftMargin: UM.Theme.getSize("thick_margin").width anchors.leftMargin: UM.Theme.getSize("thick_margin").width
color: base.statusColor color: base.statusColor
font: UM.Theme.getFont("large") font: UM.Theme.getFont("large_bold")
text: statusText text: statusText
} }
@ -179,7 +179,7 @@ Item
anchors.right: progressBar.right anchors.right: progressBar.right
color: base.statusColor color: base.statusColor
font: UM.Theme.getFont("large") font: UM.Theme.getFont("large_bold")
text: Math.round(progress) + "%" text: Math.round(progress) + "%"
visible: showProgress visible: showProgress
} }

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 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.7
@ -70,7 +70,7 @@ UM.ManagementPage
{ {
id: machineName id: machineName
text: base.currentItem && base.currentItem.name ? base.currentItem.name : "" text: base.currentItem && base.currentItem.name ? base.currentItem.name : ""
font: UM.Theme.getFont("large") font: UM.Theme.getFont("large_bold")
width: parent.width width: parent.width
elide: Text.ElideRight elide: Text.ElideRight
} }
@ -91,7 +91,7 @@ UM.ManagementPage
Item Item
{ {
width: childrenRect.width + 2 * screenScaleFactor width: Math.round(childrenRect.width + 2 * screenScaleFactor)
height: childrenRect.height height: childrenRect.height
Button Button
{ {

View File

@ -1,5 +1,5 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Uranium 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.Controls 1.4 import QtQuick.Controls 1.4
@ -10,7 +10,7 @@ import QtQuick.Dialogs 1.2
import UM 1.2 as UM import UM 1.2 as UM
import Cura 1.0 as Cura import Cura 1.0 as Cura
Rectangle Item
{ {
id: brand_section id: brand_section
@ -69,11 +69,7 @@ Rectangle
} }
style: ButtonStyle style: ButtonStyle
{ {
background: Rectangle background: Item { }
{
anchors.fill: parent
color: "transparent"
}
} }
} }
} }

View File

@ -65,7 +65,7 @@ Item
Label { Label {
text: materialProperties.name text: materialProperties.name
font: UM.Theme.getFont("large") font: UM.Theme.getFont("large_bold")
} }
} }

View File

@ -1,5 +1,5 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Uranium 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.Controls 1.4 import QtQuick.Controls 1.4
@ -15,7 +15,7 @@ Rectangle
id: materialSlot id: materialSlot
property var material: null property var material: null
property var hovered: false property var hovered: false
property var is_favorite: material != null ? material.is_favorite : false property var is_favorite: material != null && material.is_favorite
height: UM.Theme.getSize("favorites_row").height height: UM.Theme.getSize("favorites_row").height
width: parent.width width: parent.width
@ -70,24 +70,20 @@ Rectangle
} }
onClicked: onClicked:
{ {
if (materialSlot.is_favorite) { if (materialSlot.is_favorite)
{
base.materialManager.removeFavorite(material.root_material_id) base.materialManager.removeFavorite(material.root_material_id)
materialSlot.is_favorite = false
return return
} }
base.materialManager.addFavorite(material.root_material_id) base.materialManager.addFavorite(material.root_material_id)
materialSlot.is_favorite = true
return return
} }
style: ButtonStyle style: ButtonStyle
{ {
background: Rectangle background: Item { }
{
anchors.fill: parent
color: "transparent"
}
} }
UM.RecolorImage { UM.RecolorImage
{
anchors anchors
{ {
verticalCenter: favorite_button.verticalCenter verticalCenter: favorite_button.verticalCenter

View File

@ -10,7 +10,7 @@ import QtQuick.Dialogs 1.2
import UM 1.2 as UM import UM 1.2 as UM
import Cura 1.0 as Cura import Cura 1.0 as Cura
Rectangle Item
{ {
id: material_type_section id: material_type_section
property var materialType property var materialType

View File

@ -408,7 +408,7 @@ Item
{ {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_lining").width anchors.leftMargin: UM.Theme.getSize("default_lining").width
text: section == "true" ? catalog.i18nc("@label", "Protected profiles") : catalog.i18nc("@label", "Custom profiles") text: section == "true" ? catalog.i18nc("@label", "Default profiles") : catalog.i18nc("@label", "Custom profiles")
font.bold: true font.bold: true
} }
} }
@ -471,7 +471,7 @@ Item
Label { Label {
text: base.currentItemName text: base.currentItemName
font: UM.Theme.getFont("large") font: UM.Theme.getFont("large_bold")
} }
} }

View File

@ -12,7 +12,7 @@ import Cura 1.0 as Cura
import "PrinterOutput" import "PrinterOutput"
Rectangle Item
{ {
id: base id: base
UM.I18nCatalog { id: catalog; name: "cura"} UM.I18nCatalog { id: catalog; name: "cura"}

View File

@ -11,7 +11,6 @@ import Cura 1.0 as Cura
Item Item
{ {
id: customPrintSetup id: customPrintSetup
height: childrenRect.height + padding
property real padding: UM.Theme.getSize("default_margin").width property real padding: UM.Theme.getSize("default_margin").width
property bool multipleExtruders: extrudersModel.count > 1 property bool multipleExtruders: extrudersModel.count > 1
@ -98,15 +97,15 @@ Item
Rectangle Rectangle
{ {
height: UM.Theme.getSize("print_setup_widget").height
anchors anchors
{ {
top: tabBar.visible ? tabBar.bottom : globalProfileRow.bottom top: tabBar.visible ? tabBar.bottom : globalProfileRow.bottom
topMargin: -UM.Theme.getSize("default_lining").width
left: parent.left left: parent.left
leftMargin: parent.padding leftMargin: parent.padding
right: parent.right right: parent.right
rightMargin: parent.padding rightMargin: parent.padding
topMargin: -UM.Theme.getSize("default_lining").width bottom: parent.bottom
} }
z: tabBar.z - 1 z: tabBar.z - 1
// Don't show the border when only one extruder // Don't show the border when only one extruder

View File

@ -25,7 +25,7 @@ Item
right: globalProfileSelection.left right: globalProfileSelection.left
} }
text: catalog.i18nc("@label", "Profile") text: catalog.i18nc("@label", "Profile")
font: UM.Theme.getFont("default") font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }

View File

@ -29,4 +29,7 @@ Cura.ExpandableComponent
property var extrudersModel: CuraApplication.getExtrudersModel() property var extrudersModel: CuraApplication.getExtrudersModel()
contentItem: PrintSetupSelectorContents {} contentItem: PrintSetupSelectorContents {}
onExpandedChanged: UM.Preferences.setValue("view/settings_visible", expanded)
Component.onCompleted: expanded = UM.Preferences.getValue("view/settings_visible")
} }

View File

@ -15,7 +15,7 @@ Item
id: content id: content
width: UM.Theme.getSize("print_setup_widget").width - 2 * UM.Theme.getSize("default_margin").width width: UM.Theme.getSize("print_setup_widget").width - 2 * UM.Theme.getSize("default_margin").width
height: childrenRect.height height: contents.height + buttonRow.height
enum Mode enum Mode
{ {
@ -71,6 +71,15 @@ Item
right: parent.right right: parent.right
top: parent.top top: parent.top
} }
height: UM.Preferences.getValue("view/settings_list_height") - UM.Theme.getSize("default_margin").height
Connections
{
target: UM.Preferences
onPreferenceChanged:
{
customPrintSetup.height = UM.Preferences.getValue("view/settings_list_height");
}
}
visible: currentModeIndex == PrintSetupSelectorContents.Mode.Custom visible: currentModeIndex == PrintSetupSelectorContents.Mode.Custom
} }
} }
@ -94,13 +103,14 @@ Item
anchors anchors
{ {
top: buttonsSeparator.bottom bottom: parent.bottom
left: parent.left left: parent.left
right: parent.right right: parent.right
} }
Cura.SecondaryButton Cura.SecondaryButton
{ {
id: recommendedButton
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.margins: parent.padding anchors.margins: parent.padding
@ -125,5 +135,47 @@ Item
visible: currentModeIndex == PrintSetupSelectorContents.Mode.Recommended visible: currentModeIndex == PrintSetupSelectorContents.Mode.Recommended
onClicked: currentModeIndex = PrintSetupSelectorContents.Mode.Custom onClicked: currentModeIndex = PrintSetupSelectorContents.Mode.Custom
} }
//Invisible area at the bottom with which you can resize the panel.
MouseArea
{
anchors
{
left: parent.left
right: parent.right
bottom: parent.bottom
top: recommendedButton.bottom
topMargin: UM.Theme.getSize("default_lining").height
}
cursorShape: Qt.SplitVCursor
visible: currentModeIndex == PrintSetupSelectorContents.Mode.Custom
drag
{
target: parent
axis: Drag.YAxis
}
onMouseYChanged:
{
if(drag.active)
{
// position of mouse relative to dropdown align vertical centre of mouse area to cursor
// v------------------------------v v------------v
var h = mouseY + buttonRow.y + content.y - height / 2 | 0;
if(h < 200 * screenScaleFactor) //Enforce a minimum size.
{
h = 200 * screenScaleFactor;
}
//Absolute mouse Y position in the window, to prevent it from going outside the window.
var mouse_absolute_y = mapToGlobal(mouseX, mouseY).y - UM.Preferences.getValue("general/window_top");
if(mouse_absolute_y > base.height)
{
h -= mouse_absolute_y - base.height;
}
UM.Preferences.setValue("view/settings_list_height", h);
}
}
}
} }
} }

View File

@ -29,6 +29,7 @@ RowLayout
} }
return "" return ""
} }
font: UM.Theme.getFont("medium")
UM.SettingPropertyProvider UM.SettingPropertyProvider
{ {
@ -43,6 +44,7 @@ RowLayout
{ {
source: UM.Theme.getIcon("category_infill") source: UM.Theme.getIcon("category_infill")
text: Cura.MachineManager.activeStack ? parseInt(infillDensity.properties.value) + "%" : "0%" text: Cura.MachineManager.activeStack ? parseInt(infillDensity.properties.value) + "%" : "0%"
font: UM.Theme.getFont("medium")
UM.SettingPropertyProvider UM.SettingPropertyProvider
{ {
@ -57,6 +59,7 @@ RowLayout
{ {
source: UM.Theme.getIcon("category_support") source: UM.Theme.getIcon("category_support")
text: supportEnabled.properties.value == "True" ? enabledText : disabledText text: supportEnabled.properties.value == "True" ? enabledText : disabledText
font: UM.Theme.getFont("medium")
UM.SettingPropertyProvider UM.SettingPropertyProvider
{ {
@ -71,6 +74,7 @@ RowLayout
{ {
source: UM.Theme.getIcon("category_adhesion") source: UM.Theme.getIcon("category_adhesion")
text: platformAdhesionType.properties.value != "skirt" && platformAdhesionType.properties.value != "none" ? enabledText : disabledText text: platformAdhesionType.properties.value != "skirt" && platformAdhesionType.properties.value != "none" ? enabledText : disabledText
font: UM.Theme.getFont("medium")
UM.SettingPropertyProvider UM.SettingPropertyProvider
{ {

View File

@ -26,6 +26,7 @@ Item
anchors.left: parent.left anchors.left: parent.left
source: UM.Theme.getIcon("category_adhesion") source: UM.Theme.getIcon("category_adhesion")
text: catalog.i18nc("@label", "Adhesion") text: catalog.i18nc("@label", "Adhesion")
font: UM.Theme.getFont("medium")
width: labelColumnWidth width: labelColumnWidth
} }

View File

@ -63,6 +63,7 @@ Item
anchors.left: parent.left anchors.left: parent.left
source: UM.Theme.getIcon("category_infill") source: UM.Theme.getIcon("category_infill")
text: catalog.i18nc("@label", "Infill") + " (%)" text: catalog.i18nc("@label", "Infill") + " (%)"
font: UM.Theme.getFont("medium")
width: labelColumnWidth width: labelColumnWidth
} }
@ -140,6 +141,7 @@ Item
Label Label
{ {
text: index text: index
font: UM.Theme.getFont("default")
visible: (index % 20) == 0 // Only show steps of 20% visible: (index % 20) == 0 // Only show steps of 20%
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
y: UM.Theme.getSize("thin_margin").height y: UM.Theme.getSize("thin_margin").height

View File

@ -39,17 +39,6 @@ Item
{ {
target: Cura.QualityProfilesDropDownMenuModel target: Cura.QualityProfilesDropDownMenuModel
onItemsChanged: qualityModel.update() onItemsChanged: qualityModel.update()
onDataChanged:
{
// If a custom profile is selected and then a user decides to change any of setting the slider should show
// the reset button. After clicking the reset button the QualityProfilesDropDownMenuModel(ListModel) is
// updated before the property isProfileCustomized is called to update.
if (Cura.SimpleModeSettingsManager.isProfileCustomized)
{
Cura.SimpleModeSettingsManager.updateIsProfileUserCreated()
}
qualityModel.update()
}
} }
Connections { Connections {
@ -97,7 +86,7 @@ Item
if (Cura.MachineManager.activeQualityType == qualityItem.quality_type) if (Cura.MachineManager.activeQualityType == qualityItem.quality_type)
{ {
// set to -1 when switching to user created profile so all ticks are clickable // set to -1 when switching to user created profile so all ticks are clickable
if (Cura.SimpleModeSettingsManager.isProfileUserCreated) if (Cura.MachineManager.hasCustomQuality)
{ {
qualityModel.qualitySliderActiveIndex = -1 qualityModel.qualitySliderActiveIndex = -1
} }
@ -184,6 +173,7 @@ Item
id: qualityRowTitle id: qualityRowTitle
source: UM.Theme.getIcon("category_layer_height") source: UM.Theme.getIcon("category_layer_height")
text: catalog.i18nc("@label", "Layer Height") text: catalog.i18nc("@label", "Layer Height")
font: UM.Theme.getFont("medium")
anchors.left: parent.left anchors.left: parent.left
anchors.right: customisedSettings.left anchors.right: customisedSettings.left
} }
@ -192,7 +182,7 @@ Item
{ {
id: customisedSettings id: customisedSettings
visible: Cura.SimpleModeSettingsManager.isProfileCustomized || Cura.SimpleModeSettingsManager.isProfileUserCreated visible: Cura.SimpleModeSettingsManager.isProfileCustomized || Cura.MachineManager.hasCustomQuality
height: visible ? UM.Theme.getSize("print_setup_icon").height : 0 height: visible ? UM.Theme.getSize("print_setup_icon").height : 0
width: height width: height
anchors anchors
@ -282,6 +272,7 @@ Item
return Math.round((settingsColumnWidth / qualityModel.totalTicks) * index - (width / 2)) return Math.round((settingsColumnWidth / qualityModel.totalTicks) * index - (width / 2))
} }
} }
font: UM.Theme.getFont("default")
} }
} }
} }
@ -358,7 +349,7 @@ Item
{ {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
enabled: !Cura.SimpleModeSettingsManager.isProfileUserCreated enabled: !Cura.MachineManager.hasCustomQuality
onEntered: onEntered:
{ {
var tooltipContent = catalog.i18nc("@tooltip", "This quality profile is not available for your current material and nozzle configuration. Please change these to enable this quality profile") var tooltipContent = catalog.i18nc("@tooltip", "This quality profile is not available for your current material and nozzle configuration. Please change these to enable this quality profile")
@ -417,7 +408,7 @@ Item
implicitWidth: UM.Theme.getSize("print_setup_slider_handle").width implicitWidth: UM.Theme.getSize("print_setup_slider_handle").width
implicitHeight: implicitWidth implicitHeight: implicitWidth
radius: Math.round(implicitWidth / 2) radius: Math.round(implicitWidth / 2)
visible: !Cura.SimpleModeSettingsManager.isProfileCustomized && !Cura.SimpleModeSettingsManager.isProfileUserCreated && qualityModel.existingQualityProfile visible: !Cura.SimpleModeSettingsManager.isProfileCustomized && !Cura.MachineManager.hasCustomQuality && qualityModel.existingQualityProfile
} }
} }
@ -441,7 +432,7 @@ Item
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
acceptedButtons: Qt.NoButton acceptedButtons: Qt.NoButton
enabled: !Cura.SimpleModeSettingsManager.isProfileUserCreated enabled: !Cura.MachineManager.hasCustomQuality
} }
} }
@ -451,7 +442,7 @@ Item
{ {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
visible: Cura.SimpleModeSettingsManager.isProfileUserCreated visible: Cura.MachineManager.hasCustomQuality
onEntered: onEntered:
{ {

View File

@ -27,6 +27,7 @@ Item
visible: enableSupportCheckBox.visible visible: enableSupportCheckBox.visible
source: UM.Theme.getIcon("category_support") source: UM.Theme.getIcon("category_support")
text: catalog.i18nc("@label", "Support") text: catalog.i18nc("@label", "Support")
font: UM.Theme.getFont("medium")
width: labelColumnWidth width: labelColumnWidth
} }

View File

@ -2,9 +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.Controls 1.1 import QtQuick.Controls 2.3
import QtQuick.Controls.Styles 1.1
import QtQuick.Layouts 1.1
import UM 1.0 as UM import UM 1.0 as UM
@ -43,7 +41,8 @@ UM.PointingRectangle {
base.opacity = 0; base.opacity = 0;
} }
Label { Label
{
id: label; id: label;
anchors { anchors {
top: parent.top; top: parent.top;
@ -57,5 +56,6 @@ UM.PointingRectangle {
textFormat: Text.RichText textFormat: Text.RichText
font: UM.Theme.getFont("default"); font: UM.Theme.getFont("default");
color: UM.Theme.getColor("tooltip_text"); color: UM.Theme.getColor("tooltip_text");
renderType: Text.NativeRendering
} }
} }

View File

@ -80,7 +80,7 @@ Item
id: extruderCurrentTemperature id: extruderCurrentTemperature
text: Math.round(extruderModel.hotendTemperature) + "°C" text: Math.round(extruderModel.hotendTemperature) + "°C"
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
font: UM.Theme.getFont("large") font: UM.Theme.getFont("large_bold")
anchors.right: extruderTargetTemperature.left anchors.right: extruderTargetTemperature.left
anchors.top: parent.top anchors.top: parent.top
anchors.margins: UM.Theme.getSize("default_margin").width anchors.margins: UM.Theme.getSize("default_margin").width

View File

@ -67,7 +67,7 @@ Item
{ {
id: bedCurrentTemperature id: bedCurrentTemperature
text: printerModel != null ? printerModel.bedTemperature + "°C" : "" text: printerModel != null ? printerModel.bedTemperature + "°C" : ""
font: UM.Theme.getFont("large") font: UM.Theme.getFont("large_bold")
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
anchors.right: bedTargetTemperature.left anchors.right: bedTargetTemperature.left
anchors.top: parent.top anchors.top: parent.top

View File

@ -31,7 +31,7 @@ Item
Label Label
{ {
id: outputDeviceNameLabel id: outputDeviceNameLabel
font: UM.Theme.getFont("large") font: UM.Theme.getFont("large_bold")
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
anchors.left: parent.left anchors.left: parent.left
anchors.top: parent.top anchors.top: parent.top

View File

@ -11,7 +11,7 @@ Cura.ExpandablePopup
{ {
id: machineSelector id: machineSelector
property bool isNetworkPrinter: Cura.MachineManager.activeMachineNetworkKey != "" property bool isNetworkPrinter: Cura.MachineManager.activeMachineHasRemoteConnection
property bool isPrinterConnected: Cura.MachineManager.printerConnected property bool isPrinterConnected: Cura.MachineManager.printerConnected
property var outputDevice: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null property var outputDevice: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null
@ -93,11 +93,19 @@ Cura.ExpandablePopup
width: scroll.width - scroll.leftPadding - scroll.rightPadding width: scroll.width - scroll.leftPadding - scroll.rightPadding
property real maximumHeight: UM.Theme.getSize("machine_selector_widget_content").height - buttonRow.height property real maximumHeight: UM.Theme.getSize("machine_selector_widget_content").height - buttonRow.height
onHeightChanged: // We use an extra property here, since we only want to to be informed about the content size changes.
onContentHeightChanged:
{ {
scroll.height = Math.min(height, maximumHeight) scroll.height = Math.min(contentHeight, maximumHeight)
popup.height = scroll.height + buttonRow.height popup.height = scroll.height + buttonRow.height
} }
Component.onCompleted:
{
scroll.height = Math.min(contentHeight, maximumHeight)
popup.height = scroll.height + buttonRow.height
}
} }
} }

View File

@ -7,78 +7,40 @@ import QtQuick.Controls 2.3
import UM 1.2 as UM import UM 1.2 as UM
import Cura 1.0 as Cura import Cura 1.0 as Cura
Column ListView
{ {
id: machineSelectorList id: listView
model: Cura.GlobalStacksModel {}
section.property: "hasRemoteConnection"
property real contentHeight: childrenRect.height
Label section.delegate: Label
{ {
text: catalog.i18nc("@label", "Connected printers") text: section == "true" ? catalog.i18nc("@label", "Connected printers") : catalog.i18nc("@label", "Preset printers")
visible: networkedPrintersModel.items.length > 0 width: parent.width
height: UM.Theme.getSize("action_button").height
leftPadding: UM.Theme.getSize("default_margin").width leftPadding: UM.Theme.getSize("default_margin").width
height: visible ? contentHeight + 2 * UM.Theme.getSize("default_margin").height : 0
renderType: Text.NativeRendering renderType: Text.NativeRendering
font: UM.Theme.getFont("medium") font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("text_medium") color: UM.Theme.getColor("text_medium")
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
Repeater delegate: MachineSelectorButton
{ {
id: networkedPrinters text: model.name
width: listView.width
outputDevice: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null
model: UM.ContainerStacksModel checked:
{ {
id: networkedPrintersModel // If the machine has a remote connection
filter: var result = Cura.MachineManager.activeMachineId == model.id
if (Cura.MachineManager.activeMachineHasRemoteConnection)
{ {
"type": "machine", "um_network_key": "*", "hidden": "False" result |= Cura.MachineManager.activeMachineNetworkGroupName == model.metadata["connect_group_name"]
} }
} return result
delegate: MachineSelectorButton
{
text: model.metadata["connect_group_name"]
checked: Cura.MachineManager.activeMachineNetworkGroupName == model.metadata["connect_group_name"]
outputDevice: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null
Connections
{
target: Cura.MachineManager
onActiveMachineNetworkGroupNameChanged: checked = Cura.MachineManager.activeMachineNetworkGroupName == model.metadata["connect_group_name"]
}
}
}
Label
{
text: catalog.i18nc("@label", "Preset printers")
visible: virtualPrintersModel.items.length > 0
leftPadding: UM.Theme.getSize("default_margin").width
height: visible ? contentHeight + 2 * UM.Theme.getSize("default_margin").height : 0
renderType: Text.NativeRendering
font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("text_medium")
verticalAlignment: Text.AlignVCenter
}
Repeater
{
id: virtualPrinters
model: UM.ContainerStacksModel
{
id: virtualPrintersModel
filter:
{
"type": "machine", "um_network_key": null
}
}
delegate: MachineSelectorButton
{
text: model.name
checked: Cura.MachineManager.activeMachineId == model.id
} }
} }
} }

View File

@ -19,6 +19,7 @@ Item
{ {
anchors.fill: parent anchors.fill: parent
color: UM.Theme.getColor("printer_type_label_background") color: UM.Theme.getColor("printer_type_label_background")
radius: UM.Theme.getSize("checkbox_radius").width
} }
Label Label

View File

@ -73,7 +73,7 @@ Button
text: definition.label text: definition.label
textFormat: Text.PlainText textFormat: Text.PlainText
renderType: Text.NativeRendering renderType: Text.NativeRendering
font: UM.Theme.getFont("default") font: UM.Theme.getFont("medium_bold")
color: color:
{ {
if (!base.enabled) if (!base.enabled)

63
resources/qml/ToolTip.qml Normal file
View File

@ -0,0 +1,63 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Controls 2.3
import UM 1.0 as UM
import Cura 1.0 as Cura
ToolTip
{
enum ContentAlignment
{
AlignLeft,
AlignRight
}
// Defines the alignment of the content, by default to the left
property int contentAlignment: Cura.ToolTip.ContentAlignment.AlignRight
property alias tooltipText: tooltip.text
property var targetPoint: Qt.point(parent.x, y + Math.round(height/2))
id: tooltip
text: ""
delay: 500
font: UM.Theme.getFont("default")
// If the text is not set, just set the height to 0 to prevent it from showing
height: text != "" ? label.contentHeight + 2 * UM.Theme.getSize("thin_margin").width: 0
x:
{
if (contentAlignment == Cura.ToolTip.ContentAlignment.AlignLeft)
{
return (label.width + Math.round(UM.Theme.getSize("default_arrow").width * 1.2) + padding * 2) * -1
}
return parent.width + Math.round(UM.Theme.getSize("default_arrow").width * 1.2 + padding)
}
y: Math.round(parent.height / 2 - label.height / 2 ) - padding
padding: UM.Theme.getSize("thin_margin").width
background: UM.PointingRectangle
{
id: backgroundRect
color: UM.Theme.getColor("tooltip")
target: Qt.point(targetPoint.x - tooltip.x, targetPoint.y - tooltip.y)
arrowSize: UM.Theme.getSize("default_arrow").width
}
contentItem: Label
{
id: label
text: tooltip.text
font: tooltip.font
wrapMode: Text.Wrap
textFormat: Text.RichText
color: UM.Theme.getColor("tooltip_text")
renderType: Text.NativeRendering
}
}

View File

@ -96,4 +96,11 @@ Button
height: UM.Theme.getSize("button_icon").height height: UM.Theme.getSize("button_icon").height
} }
} }
Cura.ToolTip
{
id: tooltip
tooltipText: base.text
visible: base.hovered
}
} }

View File

@ -51,7 +51,7 @@ Cura.ExpandablePopup
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
height: parent.height height: parent.height
elide: Text.ElideRight elide: Text.ElideRight
font: UM.Theme.getFont("default") font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("text_medium") color: UM.Theme.getColor("text_medium")
renderType: Text.NativeRendering renderType: Text.NativeRendering
} }
@ -68,7 +68,7 @@ Cura.ExpandablePopup
} }
height: parent.height height: parent.height
elide: Text.ElideRight elide: Text.ElideRight
font: UM.Theme.getFont("default") font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
renderType: Text.NativeRendering renderType: Text.NativeRendering
} }

View File

@ -15,4 +15,5 @@ ViewsSelector 1.0 ViewsSelector.qml
ToolbarButton 1.0 ToolbarButton.qml ToolbarButton 1.0 ToolbarButton.qml
SettingView 1.0 SettingView.qml SettingView 1.0 SettingView.qml
ProfileMenu 1.0 ProfileMenu.qml ProfileMenu 1.0 ProfileMenu.qml
CheckBox 1.0 CheckBox.qml CheckBoxWithTooltip 1.0 CheckBoxWithTooltip.qml
ToolTip 1.0 ToolTip.qml

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