diff --git a/CMakeLists.txt b/CMakeLists.txt index deb4e63935..be6c9d938e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ if(CURA_DEBUGMODE) set(_cura_debugmode "ON") endif() +set(CURA_APP_NAME "cura" CACHE STRING "Short name of Cura, used for configuration folder") set(CURA_APP_DISPLAY_NAME "Ultimaker Cura" CACHE STRING "Display name of Cura") set(CURA_VERSION "master" CACHE STRING "Version name of Cura") set(CURA_BUILDTYPE "" CACHE STRING "Build type of Cura, eg. 'PPA'") diff --git a/cura/ApplicationMetadata.py b/cura/ApplicationMetadata.py index 4cb19edb72..faa3364e08 100644 --- a/cura/ApplicationMetadata.py +++ b/cura/ApplicationMetadata.py @@ -2,14 +2,22 @@ # Cura is released under the terms of the LGPLv3 or higher. # --------- -# Genearl constants used in Cura +# General constants used in Cura # --------- +DEFAULT_CURA_APP_NAME = "cura" DEFAULT_CURA_DISPLAY_NAME = "Ultimaker Cura" DEFAULT_CURA_VERSION = "master" DEFAULT_CURA_BUILD_TYPE = "" DEFAULT_CURA_DEBUG_MODE = False DEFAULT_CURA_SDK_VERSION = "6.0.0" +try: + from cura.CuraVersion import CuraAppName # type: ignore + if CuraAppName == "": + CuraAppName = DEFAULT_CURA_APP_NAME +except ImportError: + CuraAppName = DEFAULT_CURA_APP_NAME + try: from cura.CuraVersion import CuraAppDisplayName # type: ignore if CuraAppDisplayName == "": diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index c56c77e9f1..aeb26e9713 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -156,7 +156,7 @@ class CuraApplication(QtApplication): Q_ENUMS(ResourceTypes) def __init__(self, *args, **kwargs): - super().__init__(name = "cura", + super().__init__(name = ApplicationMetadata.CuraAppName, app_display_name = ApplicationMetadata.CuraAppDisplayName, version = ApplicationMetadata.CuraVersion, api_version = ApplicationMetadata.CuraSDKVersion, diff --git a/cura/CuraVersion.py.in b/cura/CuraVersion.py.in index 770a0efd7b..1a500df248 100644 --- a/cura/CuraVersion.py.in +++ b/cura/CuraVersion.py.in @@ -1,6 +1,7 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +CuraAppName = "@CURA_APP_NAME@" CuraAppDisplayName = "@CURA_APP_DISPLAY_NAME@" CuraVersion = "@CURA_VERSION@" CuraBuildType = "@CURA_BUILDTYPE@" diff --git a/cura/GlobalStacksModel.py b/cura/GlobalStacksModel.py index afee52e769..32b0d9cead 100644 --- a/cura/GlobalStacksModel.py +++ b/cura/GlobalStacksModel.py @@ -1,12 +1,11 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from PyQt5.QtCore import pyqtProperty, Qt +from PyQt5.QtCore import Qt from UM.Qt.ListModel import ListModel -from UM.Settings.ContainerRegistry import ContainerRegistry - from cura.PrinterOutputDevice import ConnectionType +from cura.Settings.CuraContainerRegistry import CuraContainerRegistry class GlobalStacksModel(ListModel): @@ -21,14 +20,13 @@ class GlobalStacksModel(ListModel): 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) + CuraContainerRegistry.getInstance().containerAdded.connect(self._onContainerChanged) + CuraContainerRegistry.getInstance().containerMetaDataChanged.connect(self._onContainerChanged) + CuraContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChanged) self._filter_dict = {} self._update() @@ -43,19 +41,20 @@ class GlobalStacksModel(ListModel): def _update(self) -> None: items = [] - container_stacks = ContainerRegistry.getInstance().findContainerStacks(type = "machine") + container_stacks = CuraContainerRegistry.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] + has_remote_connection = False + + for connection_type in container_stack.configuredConnectionTypes: + 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()), + items.append({"name": container_stack.getMetaDataEntry("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) diff --git a/cura/LayerDataBuilder.py b/cura/LayerDataBuilder.py index d6cc81a4e9..e8d1b8c59f 100755 --- a/cura/LayerDataBuilder.py +++ b/cura/LayerDataBuilder.py @@ -7,43 +7,36 @@ from UM.Mesh.MeshBuilder import MeshBuilder from .LayerData import LayerData import numpy +from typing import Dict, Optional ## Builder class for constructing a LayerData object class LayerDataBuilder(MeshBuilder): - def __init__(self): + def __init__(self) -> None: super().__init__() - self._layers = {} - self._element_counts = {} + self._layers = {} # type: Dict[int, Layer] + self._element_counts = {} # type: Dict[int, int] - def addLayer(self, layer): + def addLayer(self, layer: int) -> None: if layer not in self._layers: self._layers[layer] = Layer(layer) - def addPolygon(self, layer, polygon_type, data, line_width, line_thickness, line_feedrate): - if layer not in self._layers: - self.addLayer(layer) + def getLayer(self, layer: int) -> Optional[Layer]: + return self._layers.get(layer) - p = LayerPolygon(self, polygon_type, data, line_width, line_thickness, line_feedrate) - self._layers[layer].polygons.append(p) - - def getLayer(self, layer): - if layer in self._layers: - return self._layers[layer] - - def getLayers(self): + def getLayers(self) -> Dict[int, Layer]: return self._layers - def getElementCounts(self): + def getElementCounts(self) -> Dict[int, int]: return self._element_counts - def setLayerHeight(self, layer, height): + def setLayerHeight(self, layer: int, height: float) -> None: if layer not in self._layers: self.addLayer(layer) self._layers[layer].setHeight(height) - def setLayerThickness(self, layer, thickness): + def setLayerThickness(self, layer: int, thickness: float) -> None: if layer not in self._layers: self.addLayer(layer) @@ -71,7 +64,7 @@ class LayerDataBuilder(MeshBuilder): vertex_offset = 0 index_offset = 0 for layer, data in sorted(self._layers.items()): - ( vertex_offset, index_offset ) = data.build( vertex_offset, index_offset, vertices, colors, line_dimensions, feedrates, extruders, line_types, indices) + vertex_offset, index_offset = data.build(vertex_offset, index_offset, vertices, colors, line_dimensions, feedrates, extruders, line_types, indices) self._element_counts[layer] = data.elementCount self.addVertices(vertices) diff --git a/cura/LayerDataDecorator.py b/cura/LayerDataDecorator.py index c04479972a..ef82d8f5cc 100644 --- a/cura/LayerDataDecorator.py +++ b/cura/LayerDataDecorator.py @@ -1,13 +1,25 @@ +# Copyright (c) 2019 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from typing import Optional from UM.Scene.SceneNodeDecorator import SceneNodeDecorator +from cura.LayerData import LayerData + + ## Simple decorator to indicate a scene node holds layer data. class LayerDataDecorator(SceneNodeDecorator): def __init__(self): super().__init__() - self._layer_data = None - - def getLayerData(self): + self._layer_data = None # type: Optional[LayerData] + + def getLayerData(self) -> Optional["LayerData"]: return self._layer_data - - def setLayerData(self, layer_data): - self._layer_data = layer_data \ No newline at end of file + + def setLayerData(self, layer_data: LayerData) -> None: + self._layer_data = layer_data + + def __deepcopy__(self, memo) -> "LayerDataDecorator": + copied_decorator = LayerDataDecorator() + copied_decorator._layer_data = self._layer_data + return copied_decorator diff --git a/cura/LayerPolygon.py b/cura/LayerPolygon.py index 1941a558ba..072d5f94f5 100644 --- a/cura/LayerPolygon.py +++ b/cura/LayerPolygon.py @@ -2,7 +2,7 @@ # Cura is released under the terms of the LGPLv3 or higher. from UM.Application import Application -from typing import Any +from typing import Any, Optional import numpy from UM.Logger import Logger @@ -26,13 +26,13 @@ class LayerPolygon: __jump_map = numpy.logical_or(numpy.logical_or(numpy.arange(__number_of_types) == NoneType, numpy.arange(__number_of_types) == MoveCombingType), numpy.arange(__number_of_types) == MoveRetractionType) ## LayerPolygon, used in ProcessSlicedLayersJob - # \param extruder + # \param extruder The position of the extruder # \param line_types array with line_types # \param data new_points # \param line_widths array with line widths # \param line_thicknesses: array with type as index and thickness as value # \param line_feedrates array with line feedrates - def __init__(self, extruder, line_types, data, line_widths, line_thicknesses, line_feedrates): + def __init__(self, extruder: int, line_types: numpy.ndarray, data: numpy.ndarray, line_widths: numpy.ndarray, line_thicknesses: numpy.ndarray, line_feedrates: numpy.ndarray) -> None: self._extruder = extruder self._types = line_types for i in range(len(self._types)): @@ -57,16 +57,16 @@ class LayerPolygon: # Buffering the colors shouldn't be necessary as it is not # re-used and can save alot of memory usage. self._color_map = LayerPolygon.getColorMap() - self._colors = self._color_map[self._types] + self._colors = self._color_map[self._types] # type: numpy.ndarray # When type is used as index returns true if type == LayerPolygon.InfillType or type == LayerPolygon.SkinType or type == LayerPolygon.SupportInfillType # Should be generated in better way, not hardcoded. self._isInfillOrSkinTypeMap = numpy.array([0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1], dtype=numpy.bool) - self._build_cache_line_mesh_mask = None - self._build_cache_needed_points = None + self._build_cache_line_mesh_mask = None # type: Optional[numpy.ndarray] + self._build_cache_needed_points = None # type: Optional[numpy.ndarray] - def buildCache(self): + def buildCache(self) -> None: # For the line mesh we do not draw Infill or Jumps. Therefore those lines are filtered out. self._build_cache_line_mesh_mask = numpy.ones(self._jump_mask.shape, dtype=bool) mesh_line_count = numpy.sum(self._build_cache_line_mesh_mask) @@ -94,10 +94,14 @@ class LayerPolygon: # \param extruders : vertex numpy array to be filled # \param line_types : vertex numpy array to be filled # \param indices : index numpy array to be filled - def build(self, vertex_offset, index_offset, vertices, colors, line_dimensions, feedrates, extruders, line_types, indices): + def build(self, vertex_offset: int, index_offset: int, vertices: numpy.ndarray, colors: numpy.ndarray, line_dimensions: numpy.ndarray, feedrates: numpy.ndarray, extruders: numpy.ndarray, line_types: numpy.ndarray, indices: numpy.ndarray) -> None: if self._build_cache_line_mesh_mask is None or self._build_cache_needed_points is None: self.buildCache() - + + if self._build_cache_line_mesh_mask is None or self._build_cache_needed_points is None: + Logger.log("w", "Failed to build cache for layer polygon") + return + line_mesh_mask = self._build_cache_line_mesh_mask needed_points_list = self._build_cache_needed_points diff --git a/cura/Machines/Models/MachineManagementModel.py b/cura/Machines/Models/MachineManagementModel.py index 7dc51f07f7..3297b8a467 100644 --- a/cura/Machines/Models/MachineManagementModel.py +++ b/cura/Machines/Models/MachineManagementModel.py @@ -52,14 +52,14 @@ class MachineManagementModel(ListModel): "um_network_key": "*", "hidden": "False"} self._network_container_stacks = ContainerRegistry.getInstance().findContainerStacks(**network_filter_printers) - self._network_container_stacks.sort(key = lambda i: i.getMetaDataEntry("connect_group_name")) + self._network_container_stacks.sort(key = lambda i: i.getMetaDataEntry("group_name", "")) for container in self._network_container_stacks: metadata = container.getMetaData().copy() if container.getBottom(): metadata["definition_name"] = container.getBottom().getName() - items.append({"name": metadata["connect_group_name"], + items.append({"name": metadata.get("group_name", ""), "id": container.getId(), "metadata": metadata, "group": catalog.i18nc("@info:title", "Network enabled printers")}) diff --git a/cura/PrinterOutput/GenericOutputController.py b/cura/PrinterOutput/GenericOutputController.py index c538ae79f8..1cb416787c 100644 --- a/cura/PrinterOutput/GenericOutputController.py +++ b/cura/PrinterOutput/GenericOutputController.py @@ -81,8 +81,8 @@ class GenericOutputController(PrinterOutputController): self._output_device.cancelPrint() pass - def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int) -> None: - self._output_device.sendCommand("M140 S%s" % temperature) + def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: float) -> None: + self._output_device.sendCommand("M140 S%s" % round(temperature)) # The API doesn't allow floating point. def _onTargetBedTemperatureChanged(self) -> None: if self._preheat_bed_timer.isActive() and self._preheat_printer and self._preheat_printer.targetBedTemperature == 0: @@ -96,14 +96,14 @@ class GenericOutputController(PrinterOutputController): except ValueError: return # Got invalid values, can't pre-heat. - self.setTargetBedTemperature(printer, temperature=temperature) + self.setTargetBedTemperature(printer, temperature = temperature) self._preheat_bed_timer.setInterval(duration * 1000) self._preheat_bed_timer.start() self._preheat_printer = printer printer.updateIsPreheating(True) def cancelPreheatBed(self, printer: "PrinterOutputModel") -> None: - self.setTargetBedTemperature(printer, temperature=0) + self.setTargetBedTemperature(printer, temperature = 0) self._preheat_bed_timer.stop() printer.updateIsPreheating(False) diff --git a/cura/PrinterOutput/PrintJobOutputModel.py b/cura/PrinterOutput/PrintJobOutputModel.py index a77ac81909..eb0d846415 100644 --- a/cura/PrinterOutput/PrintJobOutputModel.py +++ b/cura/PrinterOutput/PrintJobOutputModel.py @@ -132,8 +132,7 @@ class PrintJobOutputModel(QObject): @pyqtProperty(float, notify = timeElapsedChanged) def progress(self) -> float: - time_elapsed = max(float(self.timeElapsed), 1.0) # Prevent a division by zero exception - result = time_elapsed / self.timeTotal + result = float(self.timeElapsed) / max(self.timeTotal, 1.0) # Prevent a division by zero exception. return min(result, 1.0) # Never get a progress past 1.0 @pyqtProperty(str, notify=stateChanged) diff --git a/cura/PrinterOutput/PrinterOutputController.py b/cura/PrinterOutput/PrinterOutputController.py index cc7b78ac11..aa06ada8a3 100644 --- a/cura/PrinterOutput/PrinterOutputController.py +++ b/cura/PrinterOutput/PrinterOutputController.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018 Ultimaker B.V. +# Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from UM.Logger import Logger @@ -25,10 +25,10 @@ class PrinterOutputController: self.can_update_firmware = False self._output_device = output_device - def setTargetHotendTemperature(self, printer: "PrinterOutputModel", position: int, temperature: Union[int, float]) -> None: + def setTargetHotendTemperature(self, printer: "PrinterOutputModel", position: int, temperature: float) -> None: Logger.log("w", "Set target hotend temperature not implemented in controller") - def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int) -> None: + def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: float) -> None: Logger.log("w", "Set target bed temperature not implemented in controller") def setJobState(self, job: "PrintJobOutputModel", state: str) -> None: diff --git a/cura/PrinterOutput/PrinterOutputModel.py b/cura/PrinterOutput/PrinterOutputModel.py index 4189b9fcbd..006f76a385 100644 --- a/cura/PrinterOutput/PrinterOutputModel.py +++ b/cura/PrinterOutput/PrinterOutputModel.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018 Ultimaker B.V. +# Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant, pyqtSlot, QUrl @@ -30,8 +30,8 @@ class PrinterOutputModel(QObject): def __init__(self, output_controller: "PrinterOutputController", number_of_extruders: int = 1, parent=None, firmware_version = "") -> None: super().__init__(parent) - self._bed_temperature = -1 # Use -1 for no heated bed. - self._target_bed_temperature = 0 + self._bed_temperature = -1 # type: float # Use -1 for no heated bed. + self._target_bed_temperature = 0 # type: float self._name = "" self._key = "" # Unique identifier self._controller = output_controller @@ -188,19 +188,19 @@ class PrinterOutputModel(QObject): self.nameChanged.emit() ## Update the bed temperature. This only changes it locally. - def updateBedTemperature(self, temperature: int) -> None: + def updateBedTemperature(self, temperature: float) -> None: if self._bed_temperature != temperature: self._bed_temperature = temperature self.bedTemperatureChanged.emit() - def updateTargetBedTemperature(self, temperature: int) -> None: + def updateTargetBedTemperature(self, temperature: float) -> None: if self._target_bed_temperature != temperature: self._target_bed_temperature = temperature self.targetBedTemperatureChanged.emit() ## Set the target bed temperature. This ensures that it's actually sent to the remote. - @pyqtSlot(int) - def setTargetBedTemperature(self, temperature: int) -> None: + @pyqtSlot(float) + def setTargetBedTemperature(self, temperature: float) -> None: self._controller.setTargetBedTemperature(self, temperature) self.updateTargetBedTemperature(temperature) @@ -225,55 +225,55 @@ class PrinterOutputModel(QObject): def activePrintJob(self) -> Optional["PrintJobOutputModel"]: return self._active_print_job - @pyqtProperty(str, notify=stateChanged) + @pyqtProperty(str, notify = stateChanged) def state(self) -> str: return self._printer_state - @pyqtProperty(int, notify=bedTemperatureChanged) - def bedTemperature(self) -> int: + @pyqtProperty(float, notify = bedTemperatureChanged) + def bedTemperature(self) -> float: return self._bed_temperature - @pyqtProperty(int, notify=targetBedTemperatureChanged) - def targetBedTemperature(self) -> int: + @pyqtProperty(float, notify = targetBedTemperatureChanged) + def targetBedTemperature(self) -> float: return self._target_bed_temperature # Does the printer support pre-heating the bed at all - @pyqtProperty(bool, constant=True) + @pyqtProperty(bool, constant = True) def canPreHeatBed(self) -> bool: if self._controller: return self._controller.can_pre_heat_bed return False # Does the printer support pre-heating the bed at all - @pyqtProperty(bool, constant=True) + @pyqtProperty(bool, constant = True) def canPreHeatHotends(self) -> bool: if self._controller: return self._controller.can_pre_heat_hotends return False # Does the printer support sending raw G-code at all - @pyqtProperty(bool, constant=True) + @pyqtProperty(bool, constant = True) def canSendRawGcode(self) -> bool: if self._controller: return self._controller.can_send_raw_gcode return False # Does the printer support pause at all - @pyqtProperty(bool, constant=True) + @pyqtProperty(bool, constant = True) def canPause(self) -> bool: if self._controller: return self._controller.can_pause return False # Does the printer support abort at all - @pyqtProperty(bool, constant=True) + @pyqtProperty(bool, constant = True) def canAbort(self) -> bool: if self._controller: return self._controller.can_abort return False # Does the printer support manual control at all - @pyqtProperty(bool, constant=True) + @pyqtProperty(bool, constant = True) def canControlManually(self) -> bool: if self._controller: return self._controller.can_control_manually diff --git a/cura/Scene/GCodeListDecorator.py b/cura/Scene/GCodeListDecorator.py index 572fea6ac4..d3dadb3f23 100644 --- a/cura/Scene/GCodeListDecorator.py +++ b/cura/Scene/GCodeListDecorator.py @@ -10,10 +10,10 @@ class GCodeListDecorator(SceneNodeDecorator): def getGCodeList(self) -> List[str]: return self._gcode_list - def setGCodeList(self, list: List[str]): + def setGCodeList(self, list: List[str]) -> None: self._gcode_list = list def __deepcopy__(self, memo) -> "GCodeListDecorator": copied_decorator = GCodeListDecorator() copied_decorator.setGCodeList(self.getGCodeList()) - return copied_decorator \ No newline at end of file + return copied_decorator diff --git a/cura/Settings/ExtrudersModel.py b/cura/Settings/ExtrudersModel.py index 076cebf60d..afc2af94b3 100644 --- a/cura/Settings/ExtrudersModel.py +++ b/cura/Settings/ExtrudersModel.py @@ -107,17 +107,19 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): # that signal. Application.globalContainerStackChanged doesn't fill this # signal; it's assumed to be the current printer in that case. def _extrudersChanged(self, machine_id = None): + machine_manager = Application.getInstance().getMachineManager() if machine_id is not None: - if Application.getInstance().getGlobalContainerStack() is None: + if machine_manager.activeMachine is None: # No machine, don't need to update the current machine's extruders return - if machine_id != Application.getInstance().getGlobalContainerStack().getId(): + if machine_id != machine_manager.activeMachine.getId(): # Not the current machine return # Unlink from old extruders for extruder in self._active_machine_extruders: extruder.containersChanged.disconnect(self._onExtruderStackContainersChanged) + extruder.enabledChanged.disconnect(self._updateExtruders) # Link to new extruders self._active_machine_extruders = [] @@ -126,6 +128,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): if extruder is None: #This extruder wasn't loaded yet. This happens asynchronously while this model is constructed from QML. continue extruder.containersChanged.connect(self._onExtruderStackContainersChanged) + extruder.enabledChanged.connect(self._updateExtruders) self._active_machine_extruders.append(extruder) self._updateExtruders() # Since the new extruders may have different properties, update our own model. diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py index 8dba0f5204..3940af7ecc 100755 --- a/cura/Settings/GlobalStack.py +++ b/cura/Settings/GlobalStack.py @@ -42,7 +42,12 @@ class GlobalStack(CuraContainerStack): # Per thread we have our own resolving_settings, or strange things sometimes occur. self._resolving_settings = defaultdict(set) #type: Dict[str, Set[str]] # keys are thread names + # Since the metadatachanged is defined in container stack, we can't use it here as a notifier for pyqt + # properties. So we need to tie them together like this. + self.metaDataChanged.connect(self.configuredConnectionTypesChanged) + extrudersChanged = pyqtSignal() + configuredConnectionTypesChanged = pyqtSignal() ## Get the list of extruders of this stack. # @@ -63,6 +68,37 @@ class GlobalStack(CuraContainerStack): def getLoadingPriority(cls) -> int: return 2 + ## The configured connection types can be used to find out if the global + # stack is configured to be connected with a printer, without having to + # know all the details as to how this is exactly done (and without + # actually setting the stack to be active). + # + # This data can then in turn also be used when the global stack is active; + # If we can't get a network connection, but it is configured to have one, + # we can display a different icon to indicate the difference. + @pyqtProperty("QVariantList", notify=configuredConnectionTypesChanged) + def configuredConnectionTypes(self) -> List[int]: + # Requesting it from the metadata actually gets them as strings (as that's what you get from serializing). + # But we do want them returned as a list of ints (so the rest of the code can directly compare) + connection_types = self.getMetaDataEntry("connection_type", "").split(",") + return [int(connection_type) for connection_type in connection_types if connection_type != ""] + + ## \sa configuredConnectionTypes + def addConfiguredConnectionType(self, connection_type: int) -> None: + configured_connection_types = self.configuredConnectionTypes + if connection_type not in configured_connection_types: + # Store the values as a string. + configured_connection_types.append(connection_type) + self.setMetaDataEntry("connection_type", ",".join([str(c_type) for c_type in configured_connection_types])) + + ## \sa configuredConnectionTypes + def removeConfiguredConnectionType(self, connection_type: int) -> None: + configured_connection_types = self.configuredConnectionTypes + if connection_type in self.configured_connection_types: + # Store the values as a string. + configured_connection_types.remove(connection_type) + self.setMetaDataEntry("connection_type", ",".join([str(c_type) for c_type in configured_connection_types])) + @classmethod def getConfigurationTypeFromSerialized(cls, serialized: str) -> Optional[str]: configuration_type = super().getConfigurationTypeFromSerialized(serialized) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 5763d2bcab..6d41220cad 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -115,10 +115,6 @@ class MachineManager(QObject): self._application.callLater(self.setInitialActiveMachine) - self._material_incompatible_message = Message(catalog.i18nc("@info:status", - "The selected material is incompatible with the selected machine or configuration."), - title = catalog.i18nc("@info:title", "Incompatible Material")) # type: Message - containers = CuraContainerRegistry.getInstance().findInstanceContainers(id = self.activeMaterialId) # type: List[InstanceContainer] if containers: containers[0].nameChanged.connect(self._onMaterialNameChanged) @@ -505,7 +501,7 @@ class MachineManager(QObject): @pyqtProperty(str, notify = globalContainerChanged) def activeMachineName(self) -> str: if self._global_container_stack: - return self._global_container_stack.getName() + return self._global_container_stack.getMetaDataEntry("group_name", self._global_container_stack.getName()) return "" @pyqtProperty(str, notify = globalContainerChanged) @@ -521,10 +517,20 @@ class MachineManager(QObject): @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] + has_remote_connection = False + + for connection_type in self._global_container_stack.configuredConnectionTypes: + has_remote_connection |= connection_type in [ConnectionType.NetworkConnection.value, + ConnectionType.CloudConnection.value] + return has_remote_connection return False + @pyqtProperty("QVariantList", notify=globalContainerChanged) + def activeMachineConfiguredConnectionTypes(self): + if self._global_container_stack: + return self._global_container_stack.configuredConnectionTypes + return [] + @pyqtProperty(bool, notify = printerConnectedStatusChanged) def activeMachineIsGroup(self) -> bool: return bool(self._printer_output_devices) and len(self._printer_output_devices[0].printers) > 1 @@ -547,7 +553,7 @@ class MachineManager(QObject): @pyqtProperty(str, notify = printerConnectedStatusChanged) def activeMachineNetworkGroupName(self) -> str: if self._global_container_stack: - return self._global_container_stack.getMetaDataEntry("connect_group_name", "") + return self._global_container_stack.getMetaDataEntry("group_name", "") return "" @pyqtProperty(QObject, notify = globalContainerChanged) @@ -1339,7 +1345,7 @@ class MachineManager(QObject): if not new_machine: return new_machine.setMetaDataEntry("um_network_key", self.activeMachineNetworkKey()) - new_machine.setMetaDataEntry("connect_group_name", self.activeMachineNetworkGroupName) + new_machine.setMetaDataEntry("group_name", self.activeMachineNetworkGroupName) new_machine.setMetaDataEntry("hidden", False) new_machine.setMetaDataEntry("connection_type", self._global_container_stack.getMetaDataEntry("connection_type")) else: @@ -1358,25 +1364,56 @@ class MachineManager(QObject): self.blurSettings.emit() with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): self.switchPrinterType(configuration.printerType) + + used_extruder_stack_list = ExtruderManager.getInstance().getUsedExtruderStacks() + disabled_used_extruder_position_set = set() + extruders_to_disable = set() + + # If an extruder that's currently used to print a model gets disabled due to the syncing, we need to show + # a message explaining why. + need_to_show_message = False + + for extruder_configuration in configuration.extruderConfigurations: + extruder_has_hotend = extruder_configuration.hotendID != "" + extruder_has_material = extruder_configuration.material.guid != "" + + # If the machine doesn't have a hotend or material, disable this extruder + if not extruder_has_hotend or not extruder_has_material: + extruders_to_disable.add(extruder_configuration.position) + + # If there's no material and/or nozzle on the printer, enable the first extruder and disable the rest. + if len(extruders_to_disable) == len(self._global_container_stack.extruders): + extruders_to_disable.remove(min(extruders_to_disable)) + for extruder_configuration in configuration.extruderConfigurations: position = str(extruder_configuration.position) - variant_container_node = self._variant_manager.getVariantNode(self._global_container_stack.definition.getId(), extruder_configuration.hotendID) - material_container_node = self._material_manager.getMaterialNodeByType(self._global_container_stack, - position, - extruder_configuration.hotendID, - configuration.buildplateConfiguration, - extruder_configuration.material.guid) - if variant_container_node: - self._setVariantNode(position, variant_container_node) - else: - self._global_container_stack.extruders[position].variant = empty_variant_container + # If the machine doesn't have a hotend or material, disable this extruder + if int(position) in extruders_to_disable: + self._global_container_stack.extruders[position].setEnabled(False) + + need_to_show_message = True + disabled_used_extruder_position_set.add(int(position)) - if material_container_node: - self._setMaterial(position, material_container_node) else: - self._global_container_stack.extruders[position].material = empty_material_container - self.updateMaterialWithVariant(position) + variant_container_node = self._variant_manager.getVariantNode(self._global_container_stack.definition.getId(), + extruder_configuration.hotendID) + material_container_node = self._material_manager.getMaterialNodeByType(self._global_container_stack, + position, + extruder_configuration.hotendID, + configuration.buildplateConfiguration, + extruder_configuration.material.guid) + if variant_container_node: + self._setVariantNode(position, variant_container_node) + else: + self._global_container_stack.extruders[position].variant = empty_variant_container + + if material_container_node: + self._setMaterial(position, material_container_node) + else: + self._global_container_stack.extruders[position].material = empty_material_container + self._global_container_stack.extruders[position].setEnabled(True) + self.updateMaterialWithVariant(position) if configuration.buildplateConfiguration is not None: global_variant_container_node = self._variant_manager.getBuildplateVariantNode(self._global_container_stack.definition.getId(), configuration.buildplateConfiguration) @@ -1388,6 +1425,21 @@ class MachineManager(QObject): self._global_container_stack.variant = empty_variant_container self._updateQualityWithMaterial() + if need_to_show_message: + msg_str = "{extruders} is disabled because there is no material loaded. Please load a material or use custom configurations." + + # Show human-readable extruder names such as "Extruder Left", "Extruder Front" instead of "Extruder 1, 2, 3". + extruder_names = [] + for position in sorted(disabled_used_extruder_position_set): + extruder_stack = self._global_container_stack.extruders[str(position)] + extruder_name = extruder_stack.definition.getName() + extruder_names.append(extruder_name) + extruders_str = ", ".join(extruder_names) + msg_str = msg_str.format(extruders = extruders_str) + message = Message(catalog.i18nc("@info:status", msg_str), + title = catalog.i18nc("@info:title", "Extruder(s) Disabled")) + message.show() + # See if we need to show the Discard or Keep changes screen if self.hasUserSettings and self._application.getPreferences().getValue("cura/active_mode") == 1: self._application.discardOrKeepProfileChanges() @@ -1404,12 +1456,12 @@ class MachineManager(QObject): # then all the container stacks are updated, both the current and the hidden ones. def checkCorrectGroupName(self, device_id: str, group_name: str) -> None: 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 group_name is correct. If not, update all the containers connected to the same printer if self.activeMachineNetworkGroupName != group_name: metadata_filter = {"um_network_key": self.activeMachineNetworkKey()} containers = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter) for container in containers: - container.setMetaDataEntry("connect_group_name", group_name) + container.setMetaDataEntry("group_name", group_name) ## This method checks if there is an instance connected to the given network_key def existNetworkInstances(self, network_key: str) -> bool: diff --git a/cura_app.py b/cura_app.py index 8df12d771a..3224a5b99b 100755 --- a/cura_app.py +++ b/cura_app.py @@ -9,6 +9,7 @@ import os import sys from UM.Platform import Platform +from cura.ApplicationMetadata import CuraAppName parser = argparse.ArgumentParser(prog = "cura", add_help = False) @@ -22,11 +23,11 @@ known_args = vars(parser.parse_known_args()[0]) if not known_args["debug"]: def get_cura_dir_path(): if Platform.isWindows(): - return os.path.expanduser("~/AppData/Roaming/cura") + return os.path.expanduser("~/AppData/Roaming/" + CuraAppName) elif Platform.isLinux(): - return os.path.expanduser("~/.local/share/cura") + return os.path.expanduser("~/.local/share/" + CuraAppName) elif Platform.isOSX(): - return os.path.expanduser("~/Library/Logs/cura") + return os.path.expanduser("~/Library/Logs/" + CuraAppName) if hasattr(sys, "frozen"): dirpath = get_cura_dir_path() diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 55296979b5..bf190f7e39 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -500,7 +500,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): is_printer_group = False if machine_conflict: - group_name = existing_global_stack.getMetaDataEntry("connect_group_name") + group_name = existing_global_stack.getMetaDataEntry("group_name") if group_name is not None: is_printer_group = True machine_name = group_name diff --git a/plugins/CuraDrive/src/DrivePluginExtension.py b/plugins/CuraDrive/src/DrivePluginExtension.py index 060f1496f1..bcc326a133 100644 --- a/plugins/CuraDrive/src/DrivePluginExtension.py +++ b/plugins/CuraDrive/src/DrivePluginExtension.py @@ -1,9 +1,9 @@ -# Copyright (c) 2018 Ultimaker B.V. +# Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. import os from datetime import datetime -from typing import Optional, List, Dict, Any +from typing import Any, cast, Dict, List, Optional from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal @@ -68,7 +68,7 @@ class DrivePluginExtension(QObject, Extension): def showDriveWindow(self) -> None: if not self._drive_window: - plugin_dir_path = CuraApplication.getInstance().getPluginRegistry().getPluginPath("CuraDrive") + plugin_dir_path = cast(str, CuraApplication.getInstance().getPluginRegistry().getPluginPath(self.getPluginId())) # We know this plug-in exists because that's us, so this always returns str. path = os.path.join(plugin_dir_path, "src", "qml", "main.qml") self._drive_window = CuraApplication.getInstance().createQmlComponent(path, {"CuraDrive": self}) self.refreshBackups() diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py index fd56c101a0..58c00850cb 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerMessage.py @@ -28,7 +28,7 @@ class FirmwareUpdateCheckerMessage(Message): "[no_icon]", "[no_description]", button_style = Message.ActionButtonStyle.LINK, - button_align = Message.ActionButtonStyle.BUTTON_ALIGN_LEFT) + button_align = Message.ActionButtonAlignment.ALIGN_LEFT) def getMachineId(self) -> int: return self._machine_id diff --git a/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py b/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py index 59552775b6..e2b0041674 100644 --- a/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py +++ b/plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py @@ -57,7 +57,7 @@ class FirmwareUpdaterMachineAction(MachineAction): outputDeviceCanUpdateFirmwareChanged = pyqtSignal() @pyqtProperty(QObject, notify = outputDeviceCanUpdateFirmwareChanged) def firmwareUpdater(self) -> Optional["FirmwareUpdater"]: - if self._active_output_device and self._active_output_device.activePrinter and self._active_output_device.activePrinter.getController().can_update_firmware: + if self._active_output_device and self._active_output_device.activePrinter and self._active_output_device.activePrinter.getController() is not None and self._active_output_device.activePrinter.getController().can_update_firmware: self._active_firmware_updater = self._active_output_device.getFirmwareUpdater() return self._active_firmware_updater diff --git a/plugins/GCodeReader/FlavorParser.py b/plugins/GCodeReader/FlavorParser.py index baf21d47ce..f8618712a1 100644 --- a/plugins/GCodeReader/FlavorParser.py +++ b/plugins/GCodeReader/FlavorParser.py @@ -107,6 +107,8 @@ class FlavorParser: self._layer_data_builder.setLayerHeight(self._layer_number, path[0][2]) self._layer_data_builder.setLayerThickness(self._layer_number, layer_thickness) this_layer = self._layer_data_builder.getLayer(self._layer_number) + if not this_layer: + return False except ValueError: return False count = len(path) diff --git a/plugins/MonitorStage/MonitorMain.qml b/plugins/MonitorStage/MonitorMain.qml index 5fda32db9e..a73650ed6a 100644 --- a/plugins/MonitorStage/MonitorMain.qml +++ b/plugins/MonitorStage/MonitorMain.qml @@ -2,20 +2,43 @@ // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.10 -import QtQuick.Controls 1.4 - +import QtQuick.Controls 2.0 import UM 1.3 as UM import Cura 1.0 as Cura - // We show a nice overlay on the 3D viewer when the current output device has no monitor view Rectangle { id: viewportOverlay + property bool isConnected: Cura.MachineManager.activeMachineHasActiveNetworkConnection || Cura.MachineManager.activeMachineHasActiveCloudConnection + property bool isNetworkConfigurable: ["Ultimaker 3", "Ultimaker 3 Extended", "Ultimaker S5"].indexOf(Cura.MachineManager.activeMachineDefinitionName) > -1 + property bool isNetworkConfigured: + { + // Readability: + var connectedTypes = [2, 3]; + var types = Cura.MachineManager.activeMachineConfiguredConnectionTypes + + // Check if configured connection types includes either 2 or 3 (LAN or cloud) + for (var i = 0; i < types.length; i++) + { + if (connectedTypes.indexOf(types[i]) >= 0) + { + return true + } + } + return false + } + color: UM.Theme.getColor("viewport_overlay") anchors.fill: parent + UM.I18nCatalog + { + id: catalog + name: "cura" + } + // This mouse area is to prevent mouse clicks to be passed onto the scene. MouseArea { @@ -29,6 +52,8 @@ Rectangle { anchors.fill: parent } + + // CASE 1: CAN MONITOR & CONNECTED Loader { id: monitorViewComponent @@ -42,4 +67,118 @@ Rectangle sourceComponent: Cura.MachineManager.printerOutputDevices.length > 0 ? Cura.MachineManager.printerOutputDevices[0].monitorItem : null } + + // CASE 2 & 3: Empty states + Column + { + anchors + { + top: parent.top + topMargin: UM.Theme.getSize("monitor_empty_state_offset").height + horizontalCenter: parent.horizontalCenter + } + width: UM.Theme.getSize("monitor_empty_state_size").width + spacing: UM.Theme.getSize("default_margin").height + visible: monitorViewComponent.sourceComponent == null + + // CASE 2: CAN MONITOR & NOT CONNECTED + Label + { + anchors + { + horizontalCenter: parent.horizontalCenter + } + visible: isNetworkConfigured && !isConnected + text: catalog.i18nc("@info", "Please make sure your printer has a connection:\n- Check if the printer is turned on.\n- Check if the printer is connected to the network.") + font: UM.Theme.getFont("medium") + color: UM.Theme.getColor("monitor_text_primary") + wrapMode: Text.WordWrap + lineHeight: UM.Theme.getSize("monitor_text_line_large").height + lineHeightMode: Text.FixedHeight + width: contentWidth + } + + // CASE 3: CAN NOT MONITOR + Label + { + id: noNetworkLabel + anchors + { + horizontalCenter: parent.horizontalCenter + } + visible: !isNetworkConfigured + text: catalog.i18nc("@info", "Please select a network connected printer to monitor.") + font: UM.Theme.getFont("medium") + color: UM.Theme.getColor("monitor_text_primary") + wrapMode: Text.WordWrap + width: contentWidth + lineHeight: UM.Theme.getSize("monitor_text_line_large").height + lineHeightMode: Text.FixedHeight + } + Label + { + id: noNetworkUltimakerLabel + anchors + { + horizontalCenter: parent.horizontalCenter + } + visible: !isNetworkConfigured && isNetworkConfigurable + text: catalog.i18nc("@info", "Please connect your Ultimaker printer to your local network.") + font: UM.Theme.getFont("medium") + color: UM.Theme.getColor("monitor_text_primary") + wrapMode: Text.WordWrap + width: contentWidth + lineHeight: UM.Theme.getSize("monitor_text_line_large").height + lineHeightMode: Text.FixedHeight + } + Item + { + anchors + { + left: noNetworkUltimakerLabel.left + } + visible: !isNetworkConfigured && isNetworkConfigurable + height: UM.Theme.getSize("monitor_text_line").height + width: childrenRect.width + + UM.RecolorImage + { + id: externalLinkIcon + anchors.verticalCenter: parent.verticalCenter + color: UM.Theme.getColor("monitor_text_link") + source: UM.Theme.getIcon("external_link") + width: UM.Theme.getSize("monitor_external_link_icon").width + height: UM.Theme.getSize("monitor_external_link_icon").height + } + Label + { + id: manageQueueText + anchors + { + left: externalLinkIcon.right + leftMargin: UM.Theme.getSize("narrow_margin").width + verticalCenter: externalLinkIcon.verticalCenter + } + color: UM.Theme.getColor("monitor_text_link") + font: UM.Theme.getFont("medium") // 14pt, regular + linkColor: UM.Theme.getColor("monitor_text_link") + text: catalog.i18nc("@label link to technical assistance", "View user manuals online") + renderType: Text.NativeRendering + } + MouseArea + { + anchors.fill: parent + hoverEnabled: true + onClicked: Qt.openUrlExternally("https://ultimaker.com/en/resources/manuals/ultimaker-3d-printers") + onEntered: + { + manageQueueText.font.underline = true + } + onExited: + { + manageQueueText.font.underline = false + } + } + } + } } \ No newline at end of file diff --git a/plugins/PostProcessingPlugin/scripts/FilamentChange.py b/plugins/PostProcessingPlugin/scripts/FilamentChange.py index ed0f6eb174..febb93be4c 100644 --- a/plugins/PostProcessingPlugin/scripts/FilamentChange.py +++ b/plugins/PostProcessingPlugin/scripts/FilamentChange.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018 Ultimaker B.V. +# Copyright (c) 2019 Ultimaker B.V. # The PostProcessingPlugin is released under the terms of the AGPLv3 or higher. from typing import Optional, Tuple @@ -45,6 +45,22 @@ class FilamentChange(Script): "unit": "mm", "type": "float", "default_value": 300.0 + }, + "x_position": + { + "label": "X Position", + "description": "Extruder X position. The print head will move here for filament change.", + "unit": "mm", + "type": "float", + "default_value": 0 + }, + "y_position": + { + "label": "Y Position", + "description": "Extruder Y position. The print head will move here for filament change.", + "unit": "mm", + "type": "float", + "default_value": 0 } } }""" @@ -55,6 +71,8 @@ class FilamentChange(Script): layer_nums = self.getSettingValueByKey("layer_number") initial_retract = self.getSettingValueByKey("initial_retract") later_retract = self.getSettingValueByKey("later_retract") + x_pos = self.getSettingValueByKey("x_position") + y_pos = self.getSettingValueByKey("y_position") color_change = "M600" @@ -64,6 +82,12 @@ class FilamentChange(Script): if later_retract is not None and later_retract > 0.: color_change = color_change + (" L%.2f" % later_retract) + if x_pos is not None: + color_change = color_change + (" X%.2f" % x_pos) + + if y_pos is not None: + color_change = color_change + (" Y%.2f" % y_pos) + color_change = color_change + " ; Generated by FilamentChange plugin" layer_targets = layer_nums.split(",") diff --git a/plugins/PrepareStage/PrepareMain.qml b/plugins/PrepareStage/PrepareMain.qml new file mode 100644 index 0000000000..bfeb62f0e8 --- /dev/null +++ b/plugins/PrepareStage/PrepareMain.qml @@ -0,0 +1,24 @@ +//Copyright (c) 2019 Ultimaker B.V. +//Cura is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.4 +import QtQuick.Controls 1.2 +import QtQuick.Layouts 1.1 +import QtQuick.Controls.Styles 1.1 + +import UM 1.0 as UM +import Cura 1.0 as Cura + +Item +{ + id: prepareMain + + Cura.ActionPanelWidget + { + id: actionPanelWidget + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.rightMargin: UM.Theme.getSize("thick_margin").width + anchors.bottomMargin: UM.Theme.getSize("thick_margin").height + } +} \ No newline at end of file diff --git a/plugins/PrepareStage/PrepareStage.py b/plugins/PrepareStage/PrepareStage.py index b0f862dc48..c2dee9693b 100644 --- a/plugins/PrepareStage/PrepareStage.py +++ b/plugins/PrepareStage/PrepareStage.py @@ -1,13 +1,11 @@ -# Copyright (c) 2018 Ultimaker B.V. +# Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. + import os.path from UM.Application import Application from UM.PluginRegistry import PluginRegistry -from UM.Resources import Resources from cura.Stages.CuraStage import CuraStage - - ## Stage for preparing model (slicing). class PrepareStage(CuraStage): def __init__(self, parent = None): @@ -16,4 +14,6 @@ class PrepareStage(CuraStage): def _engineCreated(self): menu_component_path = os.path.join(PluginRegistry.getInstance().getPluginPath("PrepareStage"), "PrepareMenu.qml") + main_component_path = os.path.join(PluginRegistry.getInstance().getPluginPath("PrepareStage"), "PrepareMain.qml") self.addDisplayComponent("menu", menu_component_path) + self.addDisplayComponent("main", main_component_path) \ No newline at end of file diff --git a/plugins/PreviewStage/PreviewMain.qml b/plugins/PreviewStage/PreviewMain.qml index 04241783e9..1600df6d4f 100644 --- a/plugins/PreviewStage/PreviewMain.qml +++ b/plugins/PreviewStage/PreviewMain.qml @@ -1,4 +1,4 @@ -// Copyright (c) 2018 Ultimaker B.V. +// Copyright (c) 2019 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.4 @@ -9,10 +9,22 @@ import QtQuick.Controls.Styles 1.1 import UM 1.0 as UM import Cura 1.0 as Cura - -Loader +Item { - id: previewMain + Loader + { + id: previewMain - source: UM.Controller.activeView != null && UM.Controller.activeView.mainComponent != null ? UM.Controller.activeView.mainComponent : "" + source: UM.Controller.activeView != null && UM.Controller.activeView.mainComponent != null ? UM.Controller.activeView.mainComponent : "" + } + + Cura.ActionPanelWidget + { + id: actionPanelWidget + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.rightMargin: UM.Theme.getSize("thick_margin").width + anchors.bottomMargin: UM.Theme.getSize("thick_margin").height + hasPreviewButton: false + } } \ No newline at end of file diff --git a/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcaseTile.qml b/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcaseTile.qml index c8c1e56c82..3699746b86 100644 --- a/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcaseTile.qml +++ b/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcaseTile.qml @@ -49,6 +49,7 @@ Rectangle wrapMode: Text.WordWrap elide: Text.ElideRight font: UM.Theme.getFont("medium_bold") + color: UM.Theme.getColor("text") } UM.RecolorImage { diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 42cb5d52a8..7d8d359831 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -37,7 +37,7 @@ class Toolbox(QObject, Extension): self._application = application # type: CuraApplication self._sdk_version = ApplicationMetadata.CuraSDKVersion # type: Union[str, int] - self._cloud_api_version = UltimakerCloudAuthentication.CuraCloudAPIVersion # type: int + self._cloud_api_version = UltimakerCloudAuthentication.CuraCloudAPIVersion # type: str self._cloud_api_root = UltimakerCloudAuthentication.CuraCloudAPIRoot # type: str self._api_url = None # type: Optional[str] diff --git a/plugins/UFPWriter/UFPWriter.py b/plugins/UFPWriter/UFPWriter.py index b3088fc863..c0db104c82 100644 --- a/plugins/UFPWriter/UFPWriter.py +++ b/plugins/UFPWriter/UFPWriter.py @@ -95,10 +95,14 @@ class UFPWriter(MeshWriter): added_materials = [] for extruder_stack in global_stack.extruders.values(): material = extruder_stack.material - material_file_name = material.getMetaData()["base_file"] + ".xml.fdm_material" + try: + material_file_name = material.getMetaData()["base_file"] + ".xml.fdm_material" + except KeyError: + Logger.log("w", "Unable to get base_file for the material %s", material.getId()) + continue material_file_name = "/Materials/" + material_file_name - #Same material cannot be added + # The same material should not be added again. if material_file_name in added_materials: continue diff --git a/plugins/UM3NetworkPrinting/resources/qml/CameraButton.qml b/plugins/UM3NetworkPrinting/resources/qml/CameraButton.qml index 6f054f9c19..bf7690ac37 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/CameraButton.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/CameraButton.qml @@ -9,12 +9,14 @@ import Cura 1.0 as Cura Rectangle { id: base + + property var enabled: true + property var iconSource: null; - color: "#0a0850" // TODO: Theme! + color: UM.Theme.getColor("monitor_icon_primary") height: width; radius: Math.round(0.5 * width); width: 24 * screenScaleFactor; - property var enabled: true UM.RecolorImage { id: icon; @@ -22,7 +24,7 @@ Rectangle { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter; } - color: UM.Theme.getColor("primary_text"); + color: UM.Theme.getColor("monitor_icon_accent"); height: width; source: iconSource; width: Math.round(parent.width / 2); diff --git a/plugins/UM3NetworkPrinting/resources/qml/ExpandableCard.qml b/plugins/UM3NetworkPrinting/resources/qml/ExpandableCard.qml index d4c123652d..fae8280488 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/ExpandableCard.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/ExpandableCard.qml @@ -6,10 +6,11 @@ import QtQuick.Controls 2.0 import UM 1.3 as UM import Cura 1.0 as Cura -// TODO: Theme & documentation! -// The expandable component has 3 major sub components: -// * The headerItem Always visible and should hold some info about what happens if the component is expanded -// * The popupItem The content that needs to be shown if the component is expanded. +/** + * The expandable component has 3 major sub components: + * - The headerItem Always visible and should hold some info about what happens if the component is expanded + * - The popupItem The content that needs to be shown if the component is expanded. + */ Item { id: base @@ -17,10 +18,10 @@ Item property bool expanded: false property bool enabled: true property var borderWidth: 1 - property color borderColor: "#CCCCCC" - property color headerBackgroundColor: "white" - property color headerHoverColor: "#e8f2fc" - property color drawerBackgroundColor: "white" + property color borderColor: UM.Theme.getColor("monitor_card_border") + property color headerBackgroundColor: UM.Theme.getColor("monitor_icon_accent") + property color headerHoverColor: UM.Theme.getColor("monitor_card_hover") + property color drawerBackgroundColor: UM.Theme.getColor("monitor_icon_accent") property alias headerItem: header.children property alias drawerItem: drawer.children @@ -38,6 +39,7 @@ Item color: base.enabled && headerMouseArea.containsMouse ? headerHoverColor : headerBackgroundColor height: childrenRect.height width: parent.width + radius: 2 * screenScaleFactor // TODO: Theme! Behavior on color { ColorAnimation @@ -76,6 +78,7 @@ Item color: headerBackgroundColor height: base.expanded ? childrenRect.height : 0 width: parent.width + radius: 2 * screenScaleFactor // TODO: Theme! Behavior on height { NumberAnimation diff --git a/plugins/UM3NetworkPrinting/resources/qml/GenericPopUp.qml b/plugins/UM3NetworkPrinting/resources/qml/GenericPopUp.qml new file mode 100644 index 0000000000..74d9377f3e --- /dev/null +++ b/plugins/UM3NetworkPrinting/resources/qml/GenericPopUp.qml @@ -0,0 +1,227 @@ +// Copyright (c) 2018 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.2 +import QtQuick.Controls 2.0 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Dialogs 1.1 +import QtGraphicalEffects 1.0 +import UM 1.3 as UM + +/** + * This is a generic pop-up element which can be supplied with a target and a content item. The + * content item will appear to the left, right, above, or below the target depending on the value of + * the direction property + */ +Popup +{ + id: base + + /** + * The target item is what the pop-up is "tied" to, usually a button + */ + property var target + + /** + * Which direction should the pop-up "point"? + * Possible values include: + * - "up" + * - "down" + * - "left" + * - "right" + */ + property string direction: "down" + + /** + * We save the default direction so that if a pop-up was flipped but later has space (i.e. it + * moved), we can unflip it back to the default direction. + */ + property string originalDirection: "" + + /** + * Should the popup close when you click outside it? For example, this is + * disabled by the InfoBlurb component since it's opened and closed using mouse + * hovers, not clicks. + */ + property bool closeOnClick: true + + /** + * Use white for context menus, dark grey for info blurbs! + */ + property var color: "#ffffff" // TODO: Theme! + + Component.onCompleted: + { + recalculatePosition() + + // Set the direction here so it's only set once and never mutated + originalDirection = (' ' + direction).slice(1) + } + + background: Item + { + anchors.fill: parent + + DropShadow + { + anchors.fill: pointedRectangle + color: UM.Theme.getColor("monitor_shadow") + radius: UM.Theme.getSize("monitor_shadow_radius").width + source: pointedRectangle + transparentBorder: true + verticalOffset: 2 * screenScaleFactor + } + + Item + { + id: pointedRectangle + anchors + { + horizontalCenter: parent.horizontalCenter + verticalCenter: parent.verticalCenter + } + height: parent.height - 10 * screenScaleFactor // Because of the shadow + width: parent.width - 10 * screenScaleFactor // Because of the shadow + + Rectangle + { + id: point + anchors + { + horizontalCenter: + { + switch(direction) + { + case "left": + return bloop.left + case "right": + return bloop.right + default: + return bloop.horizontalCenter + } + } + verticalCenter: + { + switch(direction) + { + case "up": + return bloop.top + case "down": + return bloop.bottom + default: + return bloop.verticalCenter + } + } + } + color: base.color + height: 12 * screenScaleFactor + transform: Rotation + { + angle: 45 + origin.x: point.width / 2 + origin.y: point.height / 2 + } + width: height + } + + Rectangle + { + id: bloop + anchors + { + fill: parent + leftMargin: direction == "left" ? 8 * screenScaleFactor : 0 + rightMargin: direction == "right" ? 8 * screenScaleFactor : 0 + topMargin: direction == "up" ? 8 * screenScaleFactor : 0 + bottomMargin: direction == "down" ? 8 * screenScaleFactor : 0 + } + color: base.color + width: parent.width + } + } + } + + visible: false + onClosed: visible = false + onOpened: + { + // Flip orientation if necessary + recalculateOrientation() + + // Fix position if necessary + recalculatePosition() + + // Show the pop up + visible = true + } + closePolicy: closeOnClick ? Popup.CloseOnPressOutside : Popup.NoAutoClose + + clip: true + + padding: UM.Theme.getSize("monitor_shadow_radius").width + topPadding: direction == "up" ? padding + 8 * screenScaleFactor : padding + bottomPadding: direction == "down" ? padding + 8 * screenScaleFactor : padding + leftPadding: direction == "left" ? padding + 8 * screenScaleFactor : padding + rightPadding: direction == "right" ? padding + 8 * screenScaleFactor : padding + + function recalculatePosition() { + + // Stupid pop-up logic causes the pop-up to resize, so let's compute what it SHOULD be + const realWidth = contentItem.implicitWidth + leftPadding + rightPadding + const realHeight = contentItem.implicitHeight + topPadding + bottomPadding + + var centered = { + x: target.x + target.width / 2 - realWidth / 2, + y: target.y + target.height / 2 - realHeight / 2 + } + + switch(direction) + { + case "left": + x = target.x + target.width + y = centered.y + break + case "right": + x = target.x - realWidth + y = centered.y + break + case "up": + x = centered.x + y = target.y + target.height + break + case "down": + x = centered.x + y = target.y - realHeight + break + } + } + + function recalculateOrientation() { + var availableSpace + var targetPosition = target.mapToItem(monitorFrame, 0, 0) + + // Stupid pop-up logic causes the pop-up to resize, so let's compute what it SHOULD be + const realWidth = contentItem.implicitWidth + leftPadding + rightPadding + const realHeight = contentItem.implicitHeight + topPadding + bottomPadding + + switch(originalDirection) + { + case "up": + availableSpace = monitorFrame.height - (targetPosition.y + target.height) + direction = availableSpace < realHeight ? "down" : originalDirection + break + case "down": + availableSpace = targetPosition.y + direction = availableSpace < realHeight ? "up" : originalDirection + break + case "right": + availableSpace = targetPosition.x + direction = availableSpace < realWidth ? "left" : originalDirection + break + case "left": + availableSpace = monitorFrame.width - (targetPosition.x + target.width) + direction = availableSpace < realWidth ? "right" : originalDirection + break + } + } +} diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorBuildplateConfiguration.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorBuildplateConfiguration.qml index 192a5a7f76..d1a0c207c5 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/MonitorBuildplateConfiguration.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorBuildplateConfiguration.qml @@ -41,7 +41,7 @@ Item anchors.centerIn: parent height: parent.height width: height - color: buildplateIcon.visible > 0 ? "transparent" : "#eeeeee" // TODO: Theme! + color: buildplateIcon.visible > 0 ? "transparent" : UM.Theme.getColor("monitor_skeleton_loading") radius: Math.floor(height / 2) } @@ -49,7 +49,7 @@ Item { id: buildplateIcon anchors.centerIn: parent - color: "#0a0850" // TODO: Theme! (Standard purple) + color: UM.Theme.getColor("monitor_icon_primary") height: parent.height source: "../svg/icons/buildplate.svg" width: height @@ -60,7 +60,7 @@ Item Label { id: buildplateLabel - color: "#191919" // TODO: Theme! + color: UM.Theme.getColor("monitor_text_primary") elide: Text.ElideRight font: UM.Theme.getFont("default") // 12pt, regular text: buildplate ? buildplate : "" diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorCarousel.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorCarousel.qml index de24ee5a8c..0d7a177dd3 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/MonitorCarousel.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorCarousel.qml @@ -49,12 +49,12 @@ Item GradientStop { position: 0.0 - color: "#fff6f6f6" // TODO: Theme! + color: UM.Theme.getColor("monitor_stage_background") } GradientStop { position: 1.0 - color: "#66f6f6f6" // TODO: Theme! + color: UM.Theme.getColor("monitor_stage_background_fade") } } } @@ -82,9 +82,9 @@ Item onClicked: navigateTo(currentIndex - 1) background: Rectangle { - color: leftButton.hovered ? "#e8f2fc" : "#ffffff" // TODO: Theme! + color: leftButton.hovered ? UM.Theme.getColor("monitor_card_hover") : UM.Theme.getColor("monitor_card_background") border.width: 1 * screenScaleFactor // TODO: Theme! - border.color: "#cccccc" // TODO: Theme! + border.color: UM.Theme.getColor("monitor_card_border") radius: 2 * screenScaleFactor // TODO: Theme! } contentItem: Item @@ -97,7 +97,7 @@ Item height: width // TODO: Theme! sourceSize.width: width // TODO: Theme! sourceSize.height: width // TODO: Theme! - color: "#152950" // TODO: Theme! + color: UM.Theme.getColor("monitor_text_primary") source: UM.Theme.getIcon("arrow_left") } } @@ -161,9 +161,9 @@ Item hoverEnabled: true background: Rectangle { - color: rightButton.hovered ? "#e8f2fc" : "#ffffff" // TODO: Theme! + color: rightButton.hovered ? UM.Theme.getColor("monitor_card_hover") : UM.Theme.getColor("monitor_card_background") border.width: 1 * screenScaleFactor // TODO: Theme! - border.color: "#cccccc" // TODO: Theme! + border.color: UM.Theme.getColor("monitor_card_border") radius: 2 * screenScaleFactor // TODO: Theme! } contentItem: Item @@ -176,7 +176,7 @@ Item height: width // TODO: Theme! sourceSize.width: width // TODO: Theme! sourceSize.height: width // TODO: Theme! - color: "#152950" // TODO: Theme! + color: UM.Theme.getColor("monitor_text_primary") source: UM.Theme.getIcon("arrow_right") } } @@ -204,12 +204,12 @@ Item GradientStop { position: 0.0 - color: "#66f6f6f6" // TODO: Theme! + color: UM.Theme.getColor("monitor_stage_background_fade") } GradientStop { position: 1.0 - color: "#fff6f6f6" // TODO: Theme! + color: UM.Theme.getColor("monitor_stage_background") } } } @@ -238,7 +238,7 @@ Item { background: Rectangle { - color: model.index == currentIndex ? "#777777" : "#d8d8d8" // TODO: Theme! + color: model.index == currentIndex ? UM.Theme.getColor("monitor_carousel_dot_current") : UM.Theme.getColor("monitor_carousel_dot") radius: Math.floor(width / 2) width: 12 * screenScaleFactor // TODO: Theme! height: width // TODO: Theme! diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorContextMenu.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorContextMenu.qml new file mode 100644 index 0000000000..771bd4b8cf --- /dev/null +++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorContextMenu.qml @@ -0,0 +1,182 @@ +// Copyright (c) 2018 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.3 +import QtQuick.Controls 2.0 +import QtQuick.Dialogs 1.1 +import UM 1.3 as UM + +/** + * A MonitorInfoBlurb is an extension of the GenericPopUp used to show static information (vs. interactive context + * menus). It accepts some text (text), an item to link to to (target), and a specification of which side of the target + * to appear on (direction). It also sets the GenericPopUp's color to black, to differentiate itself from a menu. + */ +Item +{ + property alias target: popUp.target + + property var printJob: null + + GenericPopUp + { + id: popUp + + // Which way should the pop-up point? Default is up, but will flip when required + direction: "up" + + // Use dark grey for info blurbs and white for context menus + color: UM.Theme.getColor("monitor_context_menu") + + contentItem: Item + { + id: contentWrapper + implicitWidth: childrenRect.width + implicitHeight: menuItems.height + UM.Theme.getSize("default_margin").height + + Column + { + id: menuItems + width: 144 * screenScaleFactor + + anchors + { + top: parent.top + topMargin: Math.floor(UM.Theme.getSize("default_margin").height / 2) + } + + spacing: Math.floor(UM.Theme.getSize("default_margin").height / 2) + + PrintJobContextMenuItem { + onClicked: { + sendToTopConfirmationDialog.visible = true; + popUp.close(); + } + text: catalog.i18nc("@label", "Move to top"); + visible: { + if (printJob && (printJob.state == "queued" || printJob.state == "error") && !isAssigned(printJob)) { + if (OutputDevice && OutputDevice.queuedPrintJobs[0]) { + return OutputDevice.queuedPrintJobs[0].key != printJob.key; + } + } + return false; + } + } + + PrintJobContextMenuItem { + onClicked: { + deleteConfirmationDialog.visible = true; + popUp.close(); + } + text: catalog.i18nc("@label", "Delete"); + visible: { + if (!printJob) { + return false; + } + var states = ["queued", "error", "sent_to_printer"]; + return states.indexOf(printJob.state) !== -1; + } + } + + PrintJobContextMenuItem { + enabled: visible && !(printJob.state == "pausing" || printJob.state == "resuming"); + onClicked: { + if (printJob.state == "paused") { + printJob.setState("print"); + popUp.close(); + return; + } + if (printJob.state == "printing") { + printJob.setState("pause"); + popUp.close(); + return; + } + } + text: { + if (!printJob) { + return ""; + } + switch(printJob.state) { + case "paused": + return catalog.i18nc("@label", "Resume"); + case "pausing": + return catalog.i18nc("@label", "Pausing..."); + case "resuming": + return catalog.i18nc("@label", "Resuming..."); + default: + catalog.i18nc("@label", "Pause"); + } + } + visible: { + if (!printJob) { + return false; + } + var states = ["printing", "pausing", "paused", "resuming"]; + return states.indexOf(printJob.state) !== -1; + } + } + + PrintJobContextMenuItem { + enabled: visible && printJob.state !== "aborting"; + onClicked: { + abortConfirmationDialog.visible = true; + popUp.close(); + } + text: printJob && printJob.state == "aborting" ? catalog.i18nc("@label", "Aborting...") : catalog.i18nc("@label", "Abort"); + visible: { + if (!printJob) { + return false; + } + var states = ["pre_print", "printing", "pausing", "paused", "resuming"]; + return states.indexOf(printJob.state) !== -1; + } + } + } + } + } + + MessageDialog { + id: sendToTopConfirmationDialog + Component.onCompleted: visible = false + icon: StandardIcon.Warning + onYes: OutputDevice.sendJobToTop(printJob.key) + standardButtons: StandardButton.Yes | StandardButton.No + text: printJob && printJob.name ? catalog.i18nc("@label %1 is the name of a print job.", "Are you sure you want to move %1 to the top of the queue?").arg(printJob.name) : "" + title: catalog.i18nc("@window:title", "Move print job to top") + } + + MessageDialog { + id: deleteConfirmationDialog + Component.onCompleted: visible = false + icon: StandardIcon.Warning + onYes: OutputDevice.deleteJobFromQueue(printJob.key) + standardButtons: StandardButton.Yes | StandardButton.No + text: printJob && printJob.name ? catalog.i18nc("@label %1 is the name of a print job.", "Are you sure you want to delete %1?").arg(printJob.name) : "" + title: catalog.i18nc("@window:title", "Delete print job") + } + + MessageDialog { + id: abortConfirmationDialog + Component.onCompleted: visible = false + icon: StandardIcon.Warning + onYes: printJob.setState("abort") + standardButtons: StandardButton.Yes | StandardButton.No + text: printJob && printJob.name ? catalog.i18nc("@label %1 is the name of a print job.", "Are you sure you want to abort %1?").arg(printJob.name) : "" + title: catalog.i18nc("@window:title", "Abort print") + } + + function switchPopupState() { + popUp.visible ? popUp.close() : popUp.open() + } + function open() { + popUp.open() + } + function close() { + popUp.close() + } + function isAssigned(job) { + if (!job) { + return false; + } + return job.assignedPrinter ? true : false; + } +} diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorContextMenuButton.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorContextMenuButton.qml new file mode 100644 index 0000000000..3e4f1839b6 --- /dev/null +++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorContextMenuButton.qml @@ -0,0 +1,31 @@ +// Copyright (c) 2018 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.3 +import QtQuick.Controls 2.0 +import UM 1.3 as UM +import Cura 1.0 as Cura + +Button +{ + id: base + background: Rectangle + { + color: UM.Theme.getColor("viewport_background") // TODO: Theme! + height: base.height + opacity: base.down || base.hovered ? 1 : 0 + radius: Math.round(0.5 * width) + width: base.width + } + contentItem: Label { + color: UM.Theme.getColor("monitor_text_primary") + font.pixelSize: 32 * screenScaleFactor + horizontalAlignment: Text.AlignHCenter + text: base.text + verticalAlignment: Text.AlignVCenter + } + height: width + hoverEnabled: enabled + text: "\u22EE" //Unicode Three stacked points. + width: 36 * screenScaleFactor // TODO: Theme! +} \ No newline at end of file diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorExtruderConfiguration.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorExtruderConfiguration.qml index 17c0fa8651..4079f23b0a 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/MonitorExtruderConfiguration.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorExtruderConfiguration.qml @@ -36,7 +36,7 @@ Item MonitorIconExtruder { id: extruderIcon - color: "#eeeeee" // TODO: Theme! + color: UM.Theme.getColor("monitor_skeleton_loading") position: 0 } @@ -48,7 +48,7 @@ Item left: extruderIcon.right leftMargin: 12 * screenScaleFactor // TODO: Theme! } - color: materialLabel.visible > 0 ? "transparent" : "#eeeeee" // TODO: Theme! + color: materialLabel.visible > 0 ? "transparent" : UM.Theme.getColor("monitor_skeleton_loading") height: 18 * screenScaleFactor // TODO: Theme! width: Math.max(materialLabel.contentWidth, 60 * screenScaleFactor) // TODO: Theme! radius: 2 * screenScaleFactor // TODO: Theme! @@ -57,7 +57,7 @@ Item { id: materialLabel - color: "#191919" // TODO: Theme! + color: UM.Theme.getColor("monitor_text_primary") elide: Text.ElideRight font: UM.Theme.getFont("default") // 12pt, regular text: "" @@ -77,7 +77,7 @@ Item left: materialLabelWrapper.left bottom: parent.bottom } - color: printCoreLabel.visible > 0 ? "transparent" : "#eeeeee" // TODO: Theme! + color: printCoreLabel.visible > 0 ? "transparent" : UM.Theme.getColor("monitor_skeleton_loading") height: 18 * screenScaleFactor // TODO: Theme! width: Math.max(printCoreLabel.contentWidth, 36 * screenScaleFactor) // TODO: Theme! radius: 2 * screenScaleFactor // TODO: Theme! @@ -86,7 +86,7 @@ Item { id: printCoreLabel - color: "#191919" // TODO: Theme! + color: UM.Theme.getColor("monitor_text_primary") elide: Text.ElideRight font: UM.Theme.getFont("default_bold") // 12pt, bold text: "" diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorIconExtruder.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorIconExtruder.qml index f7d123ada7..c3e78317c5 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/MonitorIconExtruder.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorIconExtruder.qml @@ -39,6 +39,7 @@ Item { id: positionLabel font: UM.Theme.getFont("small") + color: UM.Theme.getColor("monitor_text_primary") height: Math.round(size / 2) horizontalAlignment: Text.AlignHCenter text: position + 1 diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorInfoBlurb.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorInfoBlurb.qml new file mode 100644 index 0000000000..21000b8bff --- /dev/null +++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorInfoBlurb.qml @@ -0,0 +1,53 @@ +// Copyright (c) 2018 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.3 +import QtQuick.Controls 2.0 +import UM 1.3 as UM + +/** + * A MonitorInfoBlurb is an extension of the GenericPopUp used to show static information (vs. interactive context + * menus). It accepts some text (text), an item to link to to (target), and a specification of which side of the target + * to appear on (direction). It also sets the GenericPopUp's color to black, to differentiate itself from a menu. + */ +Item +{ + property alias text: innerLabel.text + property alias target: popUp.target + property alias direction: popUp.direction + + GenericPopUp + { + id: popUp + + // Which way should the pop-up point? Default is up, but will flip when required + direction: "up" + + // Use dark grey for info blurbs and white for context menus + color: UM.Theme.getColor("monitor_tooltip") + + contentItem: Item + { + id: contentWrapper + implicitWidth: childrenRect.width + implicitHeight: innerLabel.contentHeight + 2 * innerLabel.padding + Label + { + id: innerLabel + padding: 12 * screenScaleFactor // TODO: Theme! + text: "" + wrapMode: Text.WordWrap + width: 240 * screenScaleFactor // TODO: Theme! + color: UM.Theme.getColor("monitor_tooltip_text") + font: UM.Theme.getFont("default") + } + } + } + + function open() { + popUp.open() + } + function close() { + popUp.close() + } +} diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobCard.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobCard.qml index f2b9c3cff7..0bf3d0ca52 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobCard.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobCard.qml @@ -4,6 +4,7 @@ import QtQuick 2.2 import QtQuick.Controls 2.0 import UM 1.3 as UM +import Cura 1.0 as Cura /** * A Print Job Card is essentially just a filled-in Expandable Card item. All @@ -21,13 +22,17 @@ Item // The print job which all other data is derived from property var printJob: null + // If the printer is a cloud printer or not. Other items base their enabled state off of this boolean. In the future + // they might not need to though. + property bool cloudConnection: Cura.MachineManager.activeMachineHasActiveCloudConnection + width: parent.width height: childrenRect.height ExpandableCard { enabled: printJob != null - borderColor: printJob.configurationChanges.length !== 0 ? "#f5a623" : "#CCCCCC" // TODO: Theme! + borderColor: printJob && printJob.configurationChanges.length !== 0 ? UM.Theme.getColor("warning") : UM.Theme.getColor("monitor_card_border") headerItem: Row { height: 48 * screenScaleFactor // TODO: Theme! @@ -49,15 +54,16 @@ Item width: 216 * screenScaleFactor // TODO: Theme! (Should match column size) Rectangle { - color: "#eeeeee" + color: UM.Theme.getColor("monitor_skeleton_loading") width: Math.round(parent.width / 2) height: parent.height visible: !printJob + radius: 2 * screenScaleFactor // TODO: Theme! } Label { text: printJob && printJob.name ? printJob.name : "" - color: "#374355" + color: UM.Theme.getColor("monitor_text_primary") elide: Text.ElideRight font: UM.Theme.getFont("medium") // 14pt, regular visible: printJob @@ -75,15 +81,16 @@ Item width: 216 * screenScaleFactor // TODO: Theme! (Should match column size) Rectangle { - color: "#eeeeee" + color: UM.Theme.getColor("monitor_skeleton_loading") width: Math.round(parent.width / 3) height: parent.height visible: !printJob + radius: 2 * screenScaleFactor // TODO: Theme! } Label { text: printJob ? OutputDevice.formatDuration(printJob.timeTotal) : "" - color: "#374355" + color: UM.Theme.getColor("monitor_text_primary") elide: Text.ElideRight font: UM.Theme.getFont("medium") // 14pt, regular visible: printJob @@ -102,17 +109,18 @@ Item Rectangle { - color: "#eeeeee" + color: UM.Theme.getColor("monitor_skeleton_loading") width: 72 * screenScaleFactor // TODO: Theme! height: parent.height visible: !printJob + radius: 2 * screenScaleFactor // TODO: Theme! } Label { id: printerAssignmentLabel anchors.verticalCenter: parent.verticalCenter - color: "#374355" + color: UM.Theme.getColor("monitor_text_primary") elide: Text.ElideRight font: UM.Theme.getFont("medium") // 14pt, regular text: { @@ -176,7 +184,7 @@ Item { id: printerConfiguration anchors.verticalCenter: parent.verticalCenter - buildplate: "Glass" + buildplate: catalog.i18nc("@label", "Glass") configurations: [ base.printJob.configuration.extruderConfigurations[0], @@ -186,7 +194,7 @@ Item } Label { text: printJob && printJob.owner ? printJob.owner : "" - color: "#374355" // TODO: Theme! + color: UM.Theme.getColor("monitor_text_primary") elide: Text.ElideRight font: UM.Theme.getFont("medium") // 14pt, regular anchors.top: printerConfiguration.top @@ -198,18 +206,52 @@ Item } } - PrintJobContextMenu + MonitorContextMenuButton { - id: contextButton + id: contextMenuButton anchors { - right: parent.right; + right: parent.right rightMargin: 8 * screenScaleFactor // TODO: Theme! top: parent.top topMargin: 8 * screenScaleFactor // TODO: Theme! } - printJob: base.printJob width: 32 * screenScaleFactor // TODO: Theme! height: 32 * screenScaleFactor // TODO: Theme! + enabled: !cloudConnection + onClicked: enabled ? contextMenu.switchPopupState() : {} + visible: + { + if (!printJob) { + return false + } + var states = ["queued", "error", "sent_to_printer", "pre_print", "printing", "pausing", "paused", "resuming"] + return states.indexOf(printJob.state) !== -1 + } + } + + MonitorContextMenu + { + id: contextMenu + printJob: base.printJob ? base.printJob : null + target: contextMenuButton + } + + // For cloud printing, add this mouse area over the disabled contextButton to indicate that it's not available + MouseArea + { + id: contextMenuDisabledButtonArea + anchors.fill: contextMenuButton + hoverEnabled: contextMenuButton.visible && !contextMenuButton.enabled + onEntered: contextMenuDisabledInfo.open() + onExited: contextMenuDisabledInfo.close() + enabled: !contextMenuButton.enabled + } + + MonitorInfoBlurb + { + id: contextMenuDisabledInfo + text: catalog.i18nc("@info", "These options are not available because you are monitoring a cloud printer.") + target: contextMenuButton } } \ No newline at end of file diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobPreview.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobPreview.qml index d0bad63258..a392571757 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobPreview.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobPreview.qml @@ -19,7 +19,7 @@ Item Rectangle { anchors.fill: parent - color: printJob ? "transparent" : "#eeeeee" // TODO: Theme! + color: printJob ? "transparent" : UM.Theme.getColor("monitor_skeleton_loading") radius: 8 // TODO: Theme! Image { diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobProgressBar.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobProgressBar.qml index d5d4705a36..2ba70268b2 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobProgressBar.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobProgressBar.qml @@ -34,15 +34,15 @@ Item { background: Rectangle { - color: "#f5f5f5" // TODO: Theme! - implicitHeight: visible ? 8 * screenScaleFactor : 0 // TODO: Theme! + color: UM.Theme.getColor("monitor_progress_bar_empty") + implicitHeight: visible ? 12 * screenScaleFactor : 0 // TODO: Theme! implicitWidth: 180 * screenScaleFactor // TODO: Theme! radius: 2 * screenScaleFactor // TODO: Theme! } progress: Rectangle { id: progressItem; - color: printJob && printJob.isActive ? "#3282ff" : "#CCCCCC" // TODO: Theme! + color: printJob && printJob.isActive ? UM.Theme.getColor("monitor_progress_bar_fill") : UM.Theme.getColor("monitor_progress_bar_deactive") radius: 2 * screenScaleFactor // TODO: Theme! } } @@ -56,7 +56,7 @@ Item leftMargin: 18 * screenScaleFactor // TODO: Theme! } text: printJob ? Math.round(printJob.progress * 100) + "%" : "0%" - color: printJob && printJob.isActive ? "#374355" : "#babac1" // TODO: Theme! + color: printJob && printJob.isActive ? UM.Theme.getColor("monitor_text_primary") : UM.Theme.getColor("monitor_text_disabled") width: contentWidth font: UM.Theme.getFont("medium") // 14pt, regular @@ -72,7 +72,7 @@ Item left: percentLabel.right leftMargin: 18 * screenScaleFactor // TODO: Theme! } - color: "#374355" // TODO: Theme! + color: UM.Theme.getColor("monitor_text_primary") font: UM.Theme.getFont("medium") // 14pt, regular text: { diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorPrinterCard.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrinterCard.qml index facfaaaaaf..0e541c484d 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/MonitorPrinterCard.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrinterCard.qml @@ -5,15 +5,14 @@ import QtQuick 2.3 import QtQuick.Controls 2.0 import QtQuick.Dialogs 1.1 import UM 1.3 as UM +import Cura 1.0 as Cura /** - * A Printer Card is has two main components: the printer portion and the print - * job portion, the latter being paired in the UI when a print job is paired - * a printer in-cluster. + * A Printer Card is has two main components: the printer portion and the print job portion, the latter being paired in + * the UI when a print job is paired a printer in-cluster. * - * NOTE: For most labels, a fixed height with vertical alignment is used to make - * layouts more deterministic (like the fixed-size textboxes used in original - * mock-ups). This is also a stand-in for CSS's 'line-height' property. Denoted + * NOTE: For most labels, a fixed height with vertical alignment is used to make layouts more deterministic (like the + * fixed-size textboxes used in original mock-ups). This is also a stand-in for CSS's 'line-height' property. Denoted * with '// FIXED-LINE-HEIGHT:'. */ Item @@ -25,11 +24,14 @@ Item property var borderSize: 1 * screenScaleFactor // TODO: Theme, and remove from here - // If the printer card's controls are enabled. This is used by the carousel - // to prevent opening the context menu or camera while the printer card is not - // "in focus" + // If the printer card's controls are enabled. This is used by the carousel to prevent opening the context menu or + // camera while the printer card is not "in focus" property var enabled: true + // If the printer is a cloud printer or not. Other items base their enabled state off of this boolean. In the future + // they might not need to though. + property bool cloudConnection: Cura.MachineManager.activeMachineHasActiveCloudConnection + width: 834 * screenScaleFactor // TODO: Theme! height: childrenRect.height @@ -37,10 +39,10 @@ Item { id: background anchors.fill: parent - color: "#FFFFFF" // TODO: Theme! + color: UM.Theme.getColor("monitor_card_background") border { - color: "#CCCCCC" // TODO: Theme! + color: UM.Theme.getColor("monitor_card_border") width: borderSize // TODO: Remove once themed } radius: 2 * screenScaleFactor // TODO: Theme! @@ -69,7 +71,7 @@ Item id: printerImage width: 108 * screenScaleFactor // TODO: Theme! height: 108 * screenScaleFactor // TODO: Theme! - color: printer ? "transparent" : "#eeeeee" // TODO: Theme! + color: printer ? "transparent" : UM.Theme.getColor("monitor_skeleton_loading") radius: 8 // TODO: Theme! Image { @@ -93,8 +95,7 @@ Item Rectangle { id: printerNameLabel - // color: "#414054" // TODO: Theme! - color: printer ? "transparent" : "#eeeeee" // TODO: Theme! + color: printer ? "transparent" : UM.Theme.getColor("monitor_skeleton_loading") height: 18 * screenScaleFactor // TODO: Theme! width: parent.width radius: 2 * screenScaleFactor // TODO: Theme! @@ -102,7 +103,7 @@ Item Label { text: printer && printer.name ? printer.name : "" - color: "#414054" // TODO: Theme! + color: UM.Theme.getColor("monitor_text_primary") elide: Text.ElideRight font: UM.Theme.getFont("large") // 16pt, bold width: parent.width @@ -116,7 +117,7 @@ Item Rectangle { - color: "#eeeeee" // TODO: Theme! + color: UM.Theme.getColor("monitor_skeleton_loading") height: 18 * screenScaleFactor // TODO: Theme! radius: 2 * screenScaleFactor // TODO: Theme! visible: !printer @@ -156,16 +157,11 @@ Item } height: 72 * screenScaleFactor // TODO: Theme!te theRect's x property } - - // TODO: Make this work. - PropertyAnimation { target: printerConfiguration; property: "visible"; to: 0; loops: Animation.Infinite; duration: 500 } } - - - PrintJobContextMenu + MonitorContextMenuButton { - id: contextButton + id: contextMenuButton anchors { right: parent.right @@ -173,15 +169,49 @@ Item top: parent.top topMargin: 12 * screenScaleFactor // TODO: Theme! } - printJob: printer ? printer.activePrintJob : null width: 36 * screenScaleFactor // TODO: Theme! height: 36 * screenScaleFactor // TODO: Theme! - enabled: base.enabled - visible: printer + enabled: !cloudConnection + + onClicked: enabled ? contextMenu.switchPopupState() : {} + visible: + { + if (!printer || !printer.activePrintJob) { + return false + } + var states = ["queued", "error", "sent_to_printer", "pre_print", "printing", "pausing", "paused", "resuming"] + return states.indexOf(printer.activePrintJob.state) !== -1 + } } + + MonitorContextMenu + { + id: contextMenu + printJob: printer ? printer.activePrintJob : null + target: contextMenuButton + } + + // For cloud printing, add this mouse area over the disabled contextButton to indicate that it's not available + MouseArea + { + id: contextMenuDisabledButtonArea + anchors.fill: contextMenuButton + hoverEnabled: contextMenuButton.visible && !contextMenuButton.enabled + onEntered: contextMenuDisabledInfo.open() + onExited: contextMenuDisabledInfo.close() + enabled: !contextMenuButton.enabled + } + + MonitorInfoBlurb + { + id: contextMenuDisabledInfo + text: catalog.i18nc("@info", "These options are not available because you are monitoring a cloud printer.") + target: contextMenuButton + } + CameraButton { - id: cameraButton; + id: cameraButton anchors { right: parent.right @@ -190,9 +220,27 @@ Item bottomMargin: 20 * screenScaleFactor // TODO: Theme! } iconSource: "../svg/icons/camera.svg" - enabled: base.enabled + enabled: !cloudConnection visible: printer } + + // For cloud printing, add this mouse area over the disabled cameraButton to indicate that it's not available + MouseArea + { + id: cameraDisabledButtonArea + anchors.fill: cameraButton + hoverEnabled: cameraButton.visible && !cameraButton.enabled + onEntered: cameraDisabledInfo.open() + onExited: cameraDisabledInfo.close() + enabled: !cameraButton.enabled + } + + MonitorInfoBlurb + { + id: cameraDisabledInfo + text: catalog.i18nc("@info", "The webcam is not available because you are monitoring a cloud printer.") + target: cameraButton + } } @@ -220,7 +268,7 @@ Item } border { - color: printer && printer.activePrintJob && printer.activePrintJob.configurationChanges.length > 0 ? "#f5a623" : "transparent" // TODO: Theme! + color: printer && printer.activePrintJob && printer.activePrintJob.configurationChanges.length > 0 ? UM.Theme.getColor("warning") : "transparent" // TODO: Theme! width: borderSize // TODO: Remove once themed } color: "transparent" // TODO: Theme! @@ -246,7 +294,7 @@ Item { verticalCenter: parent.verticalCenter } - color: printer ? "#414054" : "#aaaaaa" // TODO: Theme! + color: printer ? UM.Theme.getColor("monitor_text_primary") : UM.Theme.getColor("monitor_text_disabled") font: UM.Theme.getFont("large_bold") // 16pt, bold text: { if (!printer) { @@ -258,7 +306,7 @@ Item } if (printer && printer.state == "unreachable") { - return catalog.i18nc("@label:status", "Unavailable") + return catalog.i18nc("@label:status", "Unreachable") } if (printer && !printer.activePrintJob && printer.state == "idle") { @@ -299,10 +347,10 @@ Item Label { id: printerJobNameLabel - color: printer && printer.activePrintJob && printer.activePrintJob.isActive ? "#414054" : "#babac1" // TODO: Theme! + color: printer && printer.activePrintJob && printer.activePrintJob.isActive ? UM.Theme.getColor("monitor_text_primary") : UM.Theme.getColor("monitor_text_disabled") elide: Text.ElideRight font: UM.Theme.getFont("large") // 16pt, bold - text: printer && printer.activePrintJob ? printer.activePrintJob.name : "Untitled" // TODO: I18N + text: printer && printer.activePrintJob ? printer.activePrintJob.name : catalog.i18nc("@label", "Untitled") width: parent.width // FIXED-LINE-HEIGHT: @@ -319,10 +367,10 @@ Item topMargin: 6 * screenScaleFactor // TODO: Theme! left: printerJobNameLabel.left } - color: printer && printer.activePrintJob && printer.activePrintJob.isActive ? "#53657d" : "#babac1" // TODO: Theme! + color: printer && printer.activePrintJob && printer.activePrintJob.isActive ? UM.Theme.getColor("monitor_text_primary") : UM.Theme.getColor("monitor_text_disabled") elide: Text.ElideRight font: UM.Theme.getFont("default") // 12pt, regular - text: printer && printer.activePrintJob ? printer.activePrintJob.owner : "Anonymous" // TODO: I18N + text: printer && printer.activePrintJob ? printer.activePrintJob.owner : catalog.i18nc("@label", "Anonymous") width: parent.width // FIXED-LINE-HEIGHT: @@ -348,8 +396,9 @@ Item verticalCenter: parent.verticalCenter } font: UM.Theme.getFont("default") - text: "Requires configuration changes" + text: catalog.i18nc("@label:status", "Requires configuration changes") visible: printer && printer.activePrintJob && printer.activePrintJob.configurationChanges.length > 0 && !printerStatus.visible + color: UM.Theme.getColor("monitor_text_primary") // FIXED-LINE-HEIGHT: height: 18 * screenScaleFactor // TODO: Theme! @@ -368,13 +417,13 @@ Item } background: Rectangle { - color: "#d8d8d8" // TODO: Theme! + color: UM.Theme.getColor("monitor_secondary_button_shadow") radius: 2 * screenScaleFactor // Todo: Theme! Rectangle { anchors.fill: parent anchors.bottomMargin: 2 * screenScaleFactor // TODO: Theme! - color: detailsButton.hovered ? "#e4e4e4" : "#f0f0f0" // TODO: Theme! + color: detailsButton.hovered ? UM.Theme.getColor("monitor_secondary_button_hover") : UM.Theme.getColor("monitor_secondary_button") radius: 2 * screenScaleFactor // Todo: Theme! } } @@ -382,9 +431,9 @@ Item { anchors.fill: parent anchors.bottomMargin: 2 * screenScaleFactor // TODO: Theme! - color: "#1e66d7" // TODO: Theme! + color: UM.Theme.getColor("monitor_secondary_button_text") font: UM.Theme.getFont("medium") // 14pt, regular - text: "Details" // TODO: I18NC! + text: catalog.i18nc("@action:button","Details"); verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter height: 18 * screenScaleFactor // TODO: Theme! diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorPrinterConfiguration.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrinterConfiguration.qml index debc8b7959..dbe085e18e 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/MonitorPrinterConfiguration.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrinterConfiguration.qml @@ -37,7 +37,7 @@ Item MonitorExtruderConfiguration { - color: modelData && modelData.activeMaterial ? modelData.activeMaterial.color : "#eeeeee" // TODO: Theme! + color: modelData && modelData.activeMaterial ? modelData.activeMaterial.color : UM.Theme.getColor("monitor_skeleton_loading") material: modelData && modelData.activeMaterial ? modelData.activeMaterial.name : "" position: modelData && typeof(modelData.position) === "number" ? modelData.position : -1 // Use negative one to create empty extruder number printCore: modelData ? modelData.hotendID : "" diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorPrinterPill.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrinterPill.qml index 2408089e1e..2aeecd5a92 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/MonitorPrinterPill.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrinterPill.qml @@ -32,14 +32,14 @@ Item Rectangle { id: background anchors.fill: parent - color: printerNameLabel.visible ? "#e4e4f2" : "#eeeeee"// TODO: Theme! + color: printerNameLabel.visible ? UM.Theme.getColor("monitor_printer_family_tag") : UM.Theme.getColor("monitor_skeleton_loading") radius: 2 * screenScaleFactor // TODO: Theme! } Label { id: printerNameLabel anchors.centerIn: parent - color: "#535369" // TODO: Theme! + color: UM.Theme.getColor("monitor_text_primary") text: tagText font.pointSize: 10 // TODO: Theme! visible: text !== "" diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorQueue.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorQueue.qml index f2dc09de95..c75bd4190f 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/MonitorQueue.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorQueue.qml @@ -22,7 +22,7 @@ Item left: queuedPrintJobs.left top: parent.top } - color: UM.Theme.getColor("text") + color: UM.Theme.getColor("monitor_text_primary") font: UM.Theme.getFont("large") text: catalog.i18nc("@label", "Queued") } @@ -42,7 +42,7 @@ Item { id: externalLinkIcon anchors.verticalCenter: manageQueueLabel.verticalCenter - color: UM.Theme.getColor("text_link") + color: UM.Theme.getColor("monitor_text_link") source: UM.Theme.getIcon("external_link") width: 16 * screenScaleFactor // TODO: Theme! (Y U NO USE 18 LIKE ALL OTHER ICONS?!) height: 16 * screenScaleFactor // TODO: Theme! (Y U NO USE 18 LIKE ALL OTHER ICONS?!) @@ -56,10 +56,10 @@ Item leftMargin: 6 * screenScaleFactor // TODO: Theme! verticalCenter: externalLinkIcon.verticalCenter } - color: UM.Theme.getColor("text_link") - font: UM.Theme.getFont("default") // 12pt, regular - linkColor: UM.Theme.getColor("text_link") - text: catalog.i18nc("@label link to connect manager", "Manage queue in Cura Connect") + color: UM.Theme.getColor("monitor_text_link") + font: UM.Theme.getFont("medium") // 14pt, regular + linkColor: UM.Theme.getColor("monitor_text_link") + text: catalog.i18nc("@label link to connect manager", "Go to Cura Connect") renderType: Text.NativeRendering } } @@ -94,7 +94,7 @@ Item Label { text: catalog.i18nc("@label", "Print jobs") - color: "#666666" + color: UM.Theme.getColor("monitor_text_primary") elide: Text.ElideRight font: UM.Theme.getFont("medium") // 14pt, regular anchors.verticalCenter: parent.verticalCenter @@ -108,7 +108,7 @@ Item Label { text: catalog.i18nc("@label", "Total print time") - color: "#666666" + color: UM.Theme.getColor("monitor_text_primary") elide: Text.ElideRight font: UM.Theme.getFont("medium") // 14pt, regular anchors.verticalCenter: parent.verticalCenter @@ -122,7 +122,7 @@ Item Label { text: catalog.i18nc("@label", "Waiting for") - color: "#666666" + color: UM.Theme.getColor("monitor_text_primary") elide: Text.ElideRight font: UM.Theme.getFont("medium") // 14pt, regular anchors.verticalCenter: parent.verticalCenter @@ -160,8 +160,101 @@ Item } printJob: modelData } - model: OutputDevice.receivedPrintJobs ? OutputDevice.queuedPrintJobs : [null,null] + model: + { + // When printing over the cloud we don't recieve print jobs until there is one, so + // unless there's at least one print job we'll be stuck with skeleton loading + // indefinitely. + if (Cura.MachineManager.activeMachineHasActiveCloudConnection) + { + return OutputDevice.queuedPrintJobs + } + return OutputDevice.receivedPrintJobs ? OutputDevice.queuedPrintJobs : [null,null] + } spacing: 6 // TODO: Theme! } } + + Rectangle + { + anchors + { + horizontalCenter: parent.horizontalCenter + top: printJobQueueHeadings.bottom + topMargin: 12 * screenScaleFactor // TODO: Theme! + } + height: 48 * screenScaleFactor // TODO: Theme! + width: parent.width + color: UM.Theme.getColor("monitor_card_background") + border.color: UM.Theme.getColor("monitor_card_border") + radius: 2 * screenScaleFactor // TODO: Theme! + + visible: printJobList.model.length == 0 + + Row + { + anchors + { + left: parent.left + leftMargin: 18 * screenScaleFactor // TODO: Theme! + verticalCenter: parent.verticalCenter + } + spacing: 18 * screenScaleFactor // TODO: Theme! + height: 18 * screenScaleFactor // TODO: Theme! + + Label + { + text: "All jobs are printed." + color: UM.Theme.getColor("monitor_text_primary") + font: UM.Theme.getFont("medium") // 14pt, regular + } + + Item + { + id: viewPrintHistoryLabel + + height: 18 * screenScaleFactor // TODO: Theme! + width: childrenRect.width + + UM.RecolorImage + { + id: printHistoryIcon + anchors.verticalCenter: parent.verticalCenter + color: UM.Theme.getColor("monitor_text_link") + source: UM.Theme.getIcon("external_link") + width: 16 * screenScaleFactor // TODO: Theme! (Y U NO USE 18 LIKE ALL OTHER ICONS?!) + height: 16 * screenScaleFactor // TODO: Theme! (Y U NO USE 18 LIKE ALL OTHER ICONS?!) + } + Label + { + id: viewPrintHistoryText + anchors + { + left: printHistoryIcon.right + leftMargin: 6 * screenScaleFactor // TODO: Theme! + verticalCenter: printHistoryIcon.verticalCenter + } + color: UM.Theme.getColor("monitor_text_link") + font: UM.Theme.getFont("medium") // 14pt, regular + linkColor: UM.Theme.getColor("monitor_text_link") + text: catalog.i18nc("@label link to connect manager", "View print history") + renderType: Text.NativeRendering + } + MouseArea + { + anchors.fill: parent + hoverEnabled: true + onClicked: Cura.MachineManager.printerOutputDevices[0].openPrintJobControlPanel() + onEntered: + { + viewPrintHistoryText.font.underline = true + } + onExited: + { + viewPrintHistoryText.font.underline = false + } + } + } + } + } } \ No newline at end of file diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorStage.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorStage.qml index 8723e6f46e..59cbda7172 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/MonitorStage.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorStage.qml @@ -11,7 +11,7 @@ import QtGraphicalEffects 1.0 // This is the root component for the monitor stage. Component { - Item + Rectangle { id: monitorFrame @@ -24,6 +24,7 @@ Component } } width: maximumWidth + color: UM.Theme.getColor("monitor_stage_background") // 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.) @@ -36,24 +37,6 @@ Component name: "cura" } - LinearGradient - { - anchors.fill: parent - gradient: Gradient - { - GradientStop - { - position: 0.0 - color: "#f6f6f6" // TODO: Theme! - } - GradientStop - { - position: 1.0 - color: "#ffffff" // TODO: Theme! - } - } - } - Item { id: printers diff --git a/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml b/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml deleted file mode 100644 index 5c5c892dad..0000000000 --- a/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright (c) 2018 Ultimaker B.V. -// Cura is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.2 -import QtQuick.Controls 2.0 -import QtQuick.Controls.Styles 1.4 -import QtQuick.Dialogs 1.1 -import QtGraphicalEffects 1.0 -import UM 1.3 as UM - -Item { - id: root; - property var printJob: null; - property var started: isStarted(printJob); - property var assigned: isAssigned(printJob); - property var enabled: true - - Button { - id: button; - background: Rectangle { - color: UM.Theme.getColor("viewport_background"); // TODO: Theme! - height: button.height; - opacity: button.down || button.hovered ? 1 : 0; - radius: Math.round(0.5 * width); - width: button.width; - } - contentItem: Label { - color: UM.Theme.getColor("monitor_context_menu_dots"); - font.pixelSize: 32 * screenScaleFactor; - horizontalAlignment: Text.AlignHCenter; - text: button.text; - verticalAlignment: Text.AlignVCenter; - } - height: width; - hoverEnabled: base.enabled - onClicked: base.enabled ? parent.switchPopupState() : {} - text: "\u22EE"; //Unicode; Three stacked points. - visible: { - if (!printJob) { - return false; - } - var states = ["queued", "sent_to_printer", "pre_print", "printing", "pausing", "paused", "resuming"]; - return states.indexOf(printJob.state) !== -1; - } - width: 36 * screenScaleFactor; // TODO: Theme! - } - - Popup { - id: popup; - background: Item { - anchors.fill: parent; - - DropShadow { - anchors.fill: pointedRectangle; - color: UM.Theme.getColor("monitor_shadow"); - radius: UM.Theme.getSize("monitor_shadow_radius").width; - source: pointedRectangle; - transparentBorder: true; - verticalOffset: 2 * screenScaleFactor; - } - - Item { - id: pointedRectangle; - anchors { - horizontalCenter: parent.horizontalCenter; - verticalCenter: parent.verticalCenter; - } - height: parent.height - 10 * screenScaleFactor; // Because of the shadow - width: parent.width - 10 * screenScaleFactor; // Because of the shadow - - Rectangle { - id: point; - anchors { - right: bloop.right; - rightMargin: 24 * screenScaleFactor; - } - color: UM.Theme.getColor("monitor_context_menu_background"); - height: 14 * screenScaleFactor; - transform: Rotation { - angle: 45; - } - width: 14 * screenScaleFactor; - y: 1 * screenScaleFactor; - } - - Rectangle { - id: bloop; - anchors { - bottom: parent.bottom; - bottomMargin: 8 * screenScaleFactor; // Because of the shadow - top: parent.top; - topMargin: 8 * screenScaleFactor; // Because of the shadow + point - } - color: UM.Theme.getColor("monitor_context_menu_background"); - width: parent.width; - } - } - } - clip: true; - closePolicy: Popup.CloseOnPressOutside; - contentItem: Column { - id: popupOptions; - anchors { - top: parent.top; - topMargin: UM.Theme.getSize("default_margin").height + 10 * screenScaleFactor; // Account for the point of the box - } - height: childrenRect.height + spacing * popupOptions.children.length + UM.Theme.getSize("default_margin").height; - spacing: Math.floor(UM.Theme.getSize("default_margin").height / 2); - width: parent.width; - - PrintJobContextMenuItem { - onClicked: { - sendToTopConfirmationDialog.visible = true; - popup.close(); - } - text: catalog.i18nc("@label", "Move to top"); - visible: { - if (printJob && printJob.state == "queued" && !assigned) { - if (OutputDevice && OutputDevice.queuedPrintJobs[0]) { - return OutputDevice.queuedPrintJobs[0].key != printJob.key; - } - } - return false; - } - } - - PrintJobContextMenuItem { - onClicked: { - deleteConfirmationDialog.visible = true; - popup.close(); - } - text: catalog.i18nc("@label", "Delete"); - visible: { - if (!printJob) { - return false; - } - var states = ["queued", "sent_to_printer"]; - return states.indexOf(printJob.state) !== -1; - } - } - - PrintJobContextMenuItem { - enabled: visible && !(printJob.state == "pausing" || printJob.state == "resuming"); - onClicked: { - if (printJob.state == "paused") { - printJob.setState("print"); - popup.close(); - return; - } - if (printJob.state == "printing") { - printJob.setState("pause"); - popup.close(); - return; - } - } - text: { - if (!printJob) { - return ""; - } - switch(printJob.state) { - case "paused": - return catalog.i18nc("@label", "Resume"); - case "pausing": - return catalog.i18nc("@label", "Pausing..."); - case "resuming": - return catalog.i18nc("@label", "Resuming..."); - default: - catalog.i18nc("@label", "Pause"); - } - } - visible: { - if (!printJob) { - return false; - } - var states = ["printing", "pausing", "paused", "resuming"]; - return states.indexOf(printJob.state) !== -1; - } - } - - PrintJobContextMenuItem { - enabled: visible && printJob.state !== "aborting"; - onClicked: { - abortConfirmationDialog.visible = true; - popup.close(); - } - text: printJob && printJob.state == "aborting" ? catalog.i18nc("@label", "Aborting...") : catalog.i18nc("@label", "Abort"); - visible: { - if (!printJob) { - return false; - } - var states = ["pre_print", "printing", "pausing", "paused", "resuming"]; - return states.indexOf(printJob.state) !== -1; - } - } - } - enter: Transition { - NumberAnimation { - duration: 75; - property: "visible"; - } - } - exit: Transition { - NumberAnimation { - duration: 75; - property: "visible"; - } - } - height: contentItem.height + 2 * padding; - onClosed: visible = false; - onOpened: visible = true; - padding: UM.Theme.getSize("monitor_shadow_radius").width; - transformOrigin: Popup.Top; - visible: false; - width: 182 * screenScaleFactor; - x: (button.width - width) + 26 * screenScaleFactor; - y: button.height + 5 * screenScaleFactor; // Because shadow - } - - MessageDialog { - id: sendToTopConfirmationDialog; - Component.onCompleted: visible = false; - icon: StandardIcon.Warning; - onYes: OutputDevice.sendJobToTop(printJob.key); - standardButtons: StandardButton.Yes | StandardButton.No; - text: printJob && printJob.name ? catalog.i18nc("@label %1 is the name of a print job.", "Are you sure you want to move %1 to the top of the queue?").arg(printJob.name) : ""; - title: catalog.i18nc("@window:title", "Move print job to top"); - } - - MessageDialog { - id: deleteConfirmationDialog; - Component.onCompleted: visible = false; - icon: StandardIcon.Warning; - onYes: OutputDevice.deleteJobFromQueue(printJob.key); - standardButtons: StandardButton.Yes | StandardButton.No; - text: printJob && printJob.name ? catalog.i18nc("@label %1 is the name of a print job.", "Are you sure you want to delete %1?").arg(printJob.name) : ""; - title: catalog.i18nc("@window:title", "Delete print job"); - } - - MessageDialog { - id: abortConfirmationDialog; - Component.onCompleted: visible = false; - icon: StandardIcon.Warning; - onYes: printJob.setState("abort"); - standardButtons: StandardButton.Yes | StandardButton.No; - text: printJob && printJob.name ? catalog.i18nc("@label %1 is the name of a print job.", "Are you sure you want to abort %1?").arg(printJob.name) : ""; - title: catalog.i18nc("@window:title", "Abort print"); - } - - // Utils - function switchPopupState() { - popup.visible ? popup.close() : popup.open(); - } - function isStarted(job) { - if (!job) { - return false; - } - return ["pre_print", "printing", "pausing", "paused", "resuming", "aborting"].indexOf(job.state) !== -1; - } - function isAssigned(job) { - if (!job) { - return false; - } - return job.assignedPrinter ? true : false; - } - function getMenuLength() { - var visible = 0; - for (var i = 0; i < popupOptions.children.length; i++) { - if (popupOptions.children[i].visible) { - visible++; - } - } - return visible; - } -} diff --git a/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenuItem.qml b/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenuItem.qml index eea8fac3e1..67c82db320 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenuItem.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenuItem.qml @@ -9,10 +9,10 @@ import UM 1.3 as UM Button { background: Rectangle { opacity: parent.down || parent.hovered ? 1 : 0; - color: UM.Theme.getColor("monitor_context_menu_highlight"); + color: UM.Theme.getColor("monitor_context_menu_hover") } contentItem: Label { - color: enabled ? UM.Theme.getColor("text") : UM.Theme.getColor("text_inactive"); + color: enabled ? UM.Theme.getColor("monitor_text_primary") : UM.Theme.getColor("monitor_text_disabled"); text: parent.text horizontalAlignment: Text.AlignLeft; verticalAlignment: Text.AlignVCenter; diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index c9c78caa0f..78944d954b 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -111,7 +111,7 @@ class CloudOutputDeviceManager: stored_cluster_id = active_machine.getMetaDataEntry(self.META_CLUSTER_ID) if stored_cluster_id in self._remote_clusters: device = self._remote_clusters[stored_cluster_id] - self._connectToOutputDevice(device) + self._connectToOutputDevice(device, active_machine) Logger.log("d", "Device connected by metadata cluster ID %s", stored_cluster_id) else: self._connectByNetworkKey(active_machine) @@ -129,12 +129,13 @@ class CloudOutputDeviceManager: Logger.log("i", "Found cluster %s with network key %s", device, local_network_key) active_machine.setMetaDataEntry(self.META_CLUSTER_ID, device.key) - self._connectToOutputDevice(device) + self._connectToOutputDevice(device, active_machine) ## Connects to an output device and makes sure it is registered in the output device manager. - def _connectToOutputDevice(self, device: CloudOutputDevice) -> None: + def _connectToOutputDevice(self, device: CloudOutputDevice, active_machine: GlobalStack) -> None: device.connect() self._output_device_manager.addOutputDevice(device) + active_machine.addConfiguredConnectionType(device.connectionType.value) ## Handles an API error received from the cloud. # \param errors: The errors received diff --git a/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py b/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py index b48f9380e1..9d7d979e28 100644 --- a/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018 Ultimaker B.V. +# Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from typing import Any, cast, Tuple, Union, Optional, Dict, List @@ -13,6 +13,7 @@ from UM.FileHandler.WriteFileJob import WriteFileJob # To call the file writer from UM.Logger import Logger from UM.Settings.ContainerRegistry import ContainerRegistry from UM.i18n import i18nCatalog +from UM.Qt.Duration import Duration, DurationFormat from UM.Message import Message from UM.Scene.SceneNode import SceneNode # For typing. @@ -194,7 +195,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), lifetime = 0, dismissable = False, progress = -1, title = i18n_catalog.i18nc("@info:title", "Sending Data")) - self._progress_message.addAction("Abort", i18n_catalog.i18nc("@action:button", "Cancel"), icon = None, + self._progress_message.addAction("Abort", i18n_catalog.i18nc("@action:button", "Cancel"), icon = "", description = "") self._progress_message.actionTriggered.connect(self._progressMessageActionTriggered) self._progress_message.show() @@ -255,7 +256,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): # Treat upload progress as response. Uploading can take more than 10 seconds, so if we don't, we can get # timeout responses if this happens. self._last_response_time = time() - if self._progress_message is not None and new_progress > self._progress_message.getProgress(): + if self._progress_message is not None and new_progress != self._progress_message.getProgress(): self._progress_message.show() # Ensure that the message is visible. self._progress_message.setProgress(bytes_sent / bytes_total * 100) @@ -267,7 +268,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): i18n_catalog.i18nc("@info:status", "Print job was successfully sent to the printer."), lifetime=5, dismissable=True, title=i18n_catalog.i18nc("@info:title", "Data Sent")) - self._success_message.addAction("View", i18n_catalog.i18nc("@action:button", "View in Monitor"), icon=None, + self._success_message.addAction("View", i18n_catalog.i18nc("@action:button", "View in Monitor"), icon = "", description="") self._success_message.actionTriggered.connect(self._successMessageActionTriggered) self._success_message.show() @@ -346,12 +347,16 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): def getDateCompleted(self, time_remaining: int) -> str: return formatDateCompleted(time_remaining) + @pyqtSlot(int, result = str) + def formatDuration(self, seconds: int) -> str: + return Duration(seconds).getDisplayString(DurationFormat.Format.Short) + @pyqtSlot(str) def sendJobToTop(self, print_job_uuid: str) -> None: # This function is part of the output device (and not of the printjob output model) as this type of operation # is a modification of the cluster queue and not of the actual job. - data = "{\"to_position\": 0}" - self.put("print_jobs/{uuid}/move_to_position".format(uuid = print_job_uuid), data, on_finished=None) + data = "{\"list\": \"queued\",\"to_position\": 0}" + self.post("print_jobs/{uuid}/action/move".format(uuid = print_job_uuid), data, on_finished=None) @pyqtSlot(str) def deleteJobFromQueue(self, print_job_uuid: str) -> None: diff --git a/plugins/UM3NetworkPrinting/src/DiscoverUM3Action.py b/plugins/UM3NetworkPrinting/src/DiscoverUM3Action.py index b688ee9d7d..ecc89b3948 100644 --- a/plugins/UM3NetworkPrinting/src/DiscoverUM3Action.py +++ b/plugins/UM3NetworkPrinting/src/DiscoverUM3Action.py @@ -13,6 +13,7 @@ from UM.i18n import i18nCatalog from cura.CuraApplication import CuraApplication from cura.MachineAction import MachineAction +from cura.Settings.CuraContainerRegistry import CuraContainerRegistry from .UM3OutputDevicePlugin import UM3OutputDevicePlugin @@ -105,13 +106,13 @@ class DiscoverUM3Action(MachineAction): global_container_stack = CuraApplication.getInstance().getGlobalContainerStack() if global_container_stack: meta_data = global_container_stack.getMetaData() - if "connect_group_name" in meta_data: - previous_connect_group_name = meta_data["connect_group_name"] - global_container_stack.setMetaDataEntry("connect_group_name", group_name) + if "group_name" in meta_data: + previous_connect_group_name = meta_data["group_name"] + global_container_stack.setMetaDataEntry("group_name", group_name) # Find all the places where there is the same group name and change it accordingly - CuraApplication.getInstance().getMachineManager().replaceContainersMetadata(key = "connect_group_name", value = previous_connect_group_name, new_value = group_name) + CuraApplication.getInstance().getMachineManager().replaceContainersMetadata(key = "group_name", value = previous_connect_group_name, new_value = group_name) else: - global_container_stack.setMetaDataEntry("connect_group_name", group_name) + global_container_stack.setMetaDataEntry("group_name", group_name) # Set the default value for "hidden", which is used when you have a group with multiple types of printers global_container_stack.setMetaDataEntry("hidden", False) @@ -133,23 +134,29 @@ class DiscoverUM3Action(MachineAction): return meta_data = global_container_stack.getMetaData() - if "um_network_key" in meta_data: - previous_network_key = meta_data["um_network_key"] - global_container_stack.setMetaDataEntry("um_network_key", printer_device.key) - # Delete old authentication data. - 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_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.connectionType.value) - CuraApplication.getInstance().getMachineManager().replaceContainersMetadata(key = "connection_type", value = previous_connection_type, new_value = printer_device.connectionType.value) - else: + if "um_network_key" in meta_data: # Global stack already had a connection, but it's changed. + old_network_key = meta_data["um_network_key"] + # Since we might have a bunch of hidden stacks, we also need to change it there. + metadata_filter = {"um_network_key": old_network_key} + containers = CuraContainerRegistry.getInstance().findContainerStacks(type="machine", **metadata_filter) + + for container in containers: + container.setMetaDataEntry("um_network_key", printer_device.key) + + # Delete old authentication data. + Logger.log("d", "Removing old authentication id %s for device %s", + global_container_stack.getMetaDataEntry("network_authentication_id", None), printer_device.key) + + container.removeMetaDataEntry("network_authentication_id") + container.removeMetaDataEntry("network_authentication_key") + + # Ensure that these containers do know that they are configured for network connection + container.addConfiguredConnectionType(printer_device.connectionType.value) + + else: # Global stack didn't have a connection yet, configure it. global_container_stack.setMetaDataEntry("um_network_key", printer_device.key) - global_container_stack.setMetaDataEntry("connection_type", printer_device.connectionType.value) + global_container_stack.addConfiguredConnectionType(printer_device.connectionType.value) if self._network_plugin: # Ensure that the connection states are refreshed. diff --git a/plugins/UM3NetworkPrinting/src/LegacyUM3PrinterOutputController.py b/plugins/UM3NetworkPrinting/src/LegacyUM3PrinterOutputController.py index 702b48ce15..63167b4ffb 100644 --- a/plugins/UM3NetworkPrinting/src/LegacyUM3PrinterOutputController.py +++ b/plugins/UM3NetworkPrinting/src/LegacyUM3PrinterOutputController.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017 Ultimaker B.V. +# Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from cura.PrinterOutput.PrinterOutputController import PrinterOutputController @@ -33,9 +33,9 @@ class LegacyUM3PrinterOutputController(PrinterOutputController): data = "{\"target\": \"%s\"}" % state self._output_device.put("print_job/state", data, on_finished=None) - def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int): + def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: float): data = str(temperature) - self._output_device.put("printer/bed/temperature/target", data, on_finished=self._onPutBedTemperatureCompleted) + self._output_device.put("printer/bed/temperature/target", data, on_finished = self._onPutBedTemperatureCompleted) def _onPutBedTemperatureCompleted(self, reply): if Version(self._preheat_printer.firmwareVersion) < Version("3.5.92"): diff --git a/plugins/UM3NetworkPrinting/src/SendMaterialJob.py b/plugins/UM3NetworkPrinting/src/SendMaterialJob.py index 8cdd647a25..f0fde818c4 100644 --- a/plugins/UM3NetworkPrinting/src/SendMaterialJob.py +++ b/plugins/UM3NetworkPrinting/src/SendMaterialJob.py @@ -1,14 +1,14 @@ -# Copyright (c) 2018 Ultimaker B.V. +# Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. + import json import os from typing import Dict, TYPE_CHECKING, Set, Optional - from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest -from UM.Application import Application from UM.Job import Job from UM.Logger import Logger +from cura.CuraApplication import CuraApplication # Absolute imports don't work in plugins from .Models import ClusterMaterial, LocalMaterial @@ -86,8 +86,8 @@ class SendMaterialJob(Job): # # \param materials_to_send A set with id's of materials that must be sent. def _sendMaterials(self, materials_to_send: Set[str]) -> None: - container_registry = Application.getInstance().getContainerRegistry() - material_manager = Application.getInstance().getMaterialManager() + container_registry = CuraApplication.getInstance().getContainerRegistry() + material_manager = CuraApplication.getInstance().getMaterialManager() material_group_dict = material_manager.getAllMaterialGroups() for root_material_id in material_group_dict: @@ -166,7 +166,7 @@ class SendMaterialJob(Job): # \return a dictionary of LocalMaterial objects by GUID def _getLocalMaterials(self) -> Dict[str, LocalMaterial]: result = {} # type: Dict[str, LocalMaterial] - material_manager = Application.getInstance().getMaterialManager() + material_manager = CuraApplication.getInstance().getMaterialManager() material_group_dict = material_manager.getAllMaterialGroups() diff --git a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py index 4a510903dd..7a7670d64c 100644 --- a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py +++ b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018 Ultimaker B.V. +# Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. import json from queue import Queue @@ -9,7 +9,7 @@ from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange, ServiceInfo from PyQt5.QtNetwork import QNetworkRequest, QNetworkAccessManager from PyQt5.QtCore import QUrl -from UM.Application import Application +from cura.CuraApplication import CuraApplication from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin from UM.Logger import Logger from UM.Signal import Signal, signalemitter @@ -41,7 +41,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self.addDeviceSignal.connect(self._onAddDevice) self.removeDeviceSignal.connect(self._onRemoveDevice) - Application.getInstance().globalContainerStackChanged.connect(self.reCheckConnections) + CuraApplication.getInstance().globalContainerStackChanged.connect(self.reCheckConnections) self._discovered_devices = {} @@ -56,7 +56,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self._cluster_api_prefix = "/cluster-api/v" + self._cluster_api_version + "/" # Get list of manual instances from preferences - self._preferences = Application.getInstance().getPreferences() + self._preferences = CuraApplication.getInstance().getPreferences() self._preferences.addPreference("um3networkprinting/manual_instances", "") # A comma-separated list of ip adresses or hostnames @@ -108,7 +108,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self.resetLastManualDevice() def reCheckConnections(self): - active_machine = Application.getInstance().getGlobalContainerStack() + active_machine = CuraApplication.getInstance().getGlobalContainerStack() if not active_machine: return @@ -118,7 +118,8 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): if key == um_network_key: if not self._discovered_devices[key].isConnected(): Logger.log("d", "Attempting to connect with [%s]" % key) - active_machine.setMetaDataEntry("connection_type", self._discovered_devices[key].connectionType.value) + # It should already be set, but if it actually connects we know for sure it's supported! + active_machine.addConfiguredConnectionType(self._discovered_devices[key].connectionType.value) self._discovered_devices[key].connect() self._discovered_devices[key].connectionStateChanged.connect(self._onDeviceConnectionStateChanged) else: @@ -134,7 +135,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): return if self._discovered_devices[key].isConnected(): # Sometimes the status changes after changing the global container and maybe the device doesn't belong to this machine - um_network_key = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("um_network_key") + um_network_key = CuraApplication.getInstance().getGlobalContainerStack().getMetaDataEntry("um_network_key") if key == um_network_key: self.getOutputDeviceManager().addOutputDevice(self._discovered_devices[key]) else: @@ -244,7 +245,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): properties = device.getProperties().copy() if b"incomplete" in properties: del properties[b"incomplete"] - properties[b'cluster_size'] = len(cluster_printers_list) + properties[b"cluster_size"] = len(cluster_printers_list) self._onRemoveDevice(instance_name) self._onAddDevice(instance_name, address, properties) @@ -287,9 +288,10 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self._discovered_devices[device.getId()] = device self.discoveredDevicesChanged.emit() - global_container_stack = Application.getInstance().getGlobalContainerStack() + global_container_stack = CuraApplication.getInstance().getGlobalContainerStack() if global_container_stack and device.getId() == global_container_stack.getMetaDataEntry("um_network_key"): - global_container_stack.setMetaDataEntry("connection_type", device.connectionType.value) + # Ensure that the configured connection type is set. + global_container_stack.addConfiguredConnectionType(device.connectionType.value) device.connect() device.connectionStateChanged.connect(self._onDeviceConnectionStateChanged) @@ -306,7 +308,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self._service_changed_request_event.wait(timeout = 5.0) # Stop if the application is shutting down - if Application.getInstance().isShuttingDown(): + if CuraApplication.getInstance().isShuttingDown(): return self._service_changed_request_event.clear() diff --git a/plugins/USBPrinting/AutoDetectBaudJob.py b/plugins/USBPrinting/AutoDetectBaudJob.py index 2fa0af1795..36e9637c47 100644 --- a/plugins/USBPrinting/AutoDetectBaudJob.py +++ b/plugins/USBPrinting/AutoDetectBaudJob.py @@ -72,9 +72,9 @@ class AutoDetectBaudJob(Job): while timeout_time > time(): line = serial.readline() - if b"ok " in line and b"T:" in line: + if b"ok" in line and b"T:" in line: successful_responses += 1 - if successful_responses >= 3: + if successful_responses >= 1: self.setResult(baud_rate) Logger.log("d", "Detected baud rate {baud_rate} on serial {serial} on retry {retry} with after {time_elapsed:0.2f} seconds.".format( serial = self._serial_port, baud_rate = baud_rate, retry = retry, time_elapsed = time() - start_timeout_time)) diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index 89903b06f4..752773723e 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -55,6 +55,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice): self._update_thread = Thread(target = self._update, daemon = True) self._last_temperature_request = None # type: Optional[int] + self._firmware_idle_count = 0 self._is_printing = False # A print is being sent. @@ -114,7 +115,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice): def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None, **kwargs): if self._is_printing: return # Aleady printing - + self.writeStarted.emit(self) # cancel any ongoing preheat timer before starting a print self._printers[0].getController().stopPreheatTimers() diff --git a/plugins/VersionUpgrade/VersionUpgrade35to40/VersionUpgrade35to40.py b/plugins/VersionUpgrade/VersionUpgrade35to40/VersionUpgrade35to40.py index 52cd7cf3cb..900c0a7396 100644 --- a/plugins/VersionUpgrade/VersionUpgrade35to40/VersionUpgrade35to40.py +++ b/plugins/VersionUpgrade/VersionUpgrade35to40/VersionUpgrade35to40.py @@ -1,9 +1,12 @@ import configparser -from typing import Tuple, List, Set +from typing import Tuple, List, Set, Dict import io + from UM.VersionUpgrade import VersionUpgrade from cura.PrinterOutputDevice import ConnectionType + deleted_settings = {"bridge_wall_max_overhang"} # type: Set[str] +renamed_configurations = {"connect_group_name": "group_name"} # type: Dict[str, str] class VersionUpgrade35to40(VersionUpgrade): @@ -20,10 +23,16 @@ class VersionUpgrade35to40(VersionUpgrade): # Set the connection type if um_network_key or the octoprint key is set. parser["metadata"]["connection_type"] = str(ConnectionType.NetworkConnection.value) + if "metadata" in parser: + for old_name, new_name in renamed_configurations.items(): + if old_name not in parser["metadata"]: + continue + parser["metadata"][new_name] = parser["metadata"][old_name] + del parser["metadata"][old_name] + result = io.StringIO() parser.write(result) return [filename], [result.getvalue()] - pass def getCfgVersion(self, serialised: str) -> int: parser = configparser.ConfigParser(interpolation = None) @@ -65,4 +74,4 @@ class VersionUpgrade35to40(VersionUpgrade): result = io.StringIO() parser.write(result) - return [filename], [result.getvalue()] \ No newline at end of file + return [filename], [result.getvalue()] diff --git a/resources/definitions/creality_cr-x.def.json b/resources/definitions/creality_cr-x.def.json new file mode 100644 index 0000000000..f7eb08224c --- /dev/null +++ b/resources/definitions/creality_cr-x.def.json @@ -0,0 +1,51 @@ +{ + "id": "CR-X", + "version": 2, + "name": "Creality CR-X", + "inherits": "fdmprinter", + "metadata": { + "visible": true, + "author": "SRC", + "manufacturer": "Creality3D", + "category": "Other", + "file_formats": "text/x-gcode", + "platform": "cr-x.stl", + "has_variants": false, + "has_machine_quality": false, + "preferred_quality_type": "draft", + "machine_extruder_trains": { + "0": "cr-x_extruder_0", + "1": "cr-x_extruder_1" + } + }, + + "overrides": { + "machine_name": { "default_value": "Creality CR-X" }, + "machine_extruder_count": { "default_value": 2 }, + "machine_heated_bed": { "default_value": true }, + "machine_width": { "default_value": 300 }, + "machine_depth": { "default_value": 300 }, + "machine_height": { "default_value": 400 }, + "machine_center_is_zero": { "default_value": false }, + "retraction_amount": { "default_value": 3 }, + "retraction_speed": { "default_value": 70}, + "adhesion_type": { "default_value": "skirt" }, + "gantry_height": { "default_value": 30 }, + "speed_print": { "default_value": 60 }, + "speed_travel": { "default_value": 120 }, + "machine_max_acceleration_x": { "default_value": 500 }, + "machine_max_acceleration_y": { "default_value": 500 }, + "machine_max_acceleration_z": { "default_value": 100 }, + "machine_max_acceleration_e": { "default_value": 5000 }, + "machine_max_jerk_xy": { "default_value": 5.0 }, + "machine_max_jerk_z": { "default_value": 0.4 }, + "machine_max_jerk_e": { "default_value": 5.0 }, + "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" }, + "machine_start_gcode": { "default_value": "G21 ;metric values\nG28 ;home all\nG90 ;absolute positioning\nM107 ;start with the fan off\nG1 F2400 Z15.0 ;raise the nozzle 15mm\nM109 S{material_print_temperature} ;Set Extruder Temperature and Wait\nM190 S{material_bed_temperature}; Wait for bed temperature to reach target temp\nT0 ;Switch to Extruder 1\nG1 F3000 X5 Y10 Z0.2 ;move to prime start position\nG92 E0 ;reset extrusion distance\nG1 F600 X160 E15 ;prime nozzle in a line\nG1 F5000 X180 ;quick wipe\nG92 E0 ;reset extrusion distance" }, + "machine_end_gcode": { "default_value": "M104 S0 ;hotend off\nM140 S0 ;bed off\nG92 E0\nG1 F2000 E-100 ;retract filament 100mm\nG92 E0\nG1 F3000 X0 Y270 ;move bed for easy part removal\nM84 ;disable steppers" }, + "material_print_temperature": { "default_value": 200 }, + "wall_thickness": { "default_value": 1 }, + "top_bottom_thickness": { "default_value": 1 }, + "bottom_thickness": { "default_value": 1 } + } +} diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 836900e162..11ca11b82e 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -3395,7 +3395,7 @@ "infill": "Within Infill" }, "default_value": "all", - "resolve": "'noskin' if 'noskin' in extruderValues('retraction_combing') else ('all' if 'all' in extruderValues('retraction_combing') else 'off')", + "resolve": "'noskin' if 'noskin' in extruderValues('retraction_combing') else ('infill' if 'infill' in extruderValues('retraction_combing') else ('all' if 'all' in extruderValues('retraction_combing') else 'off'))", "settable_per_mesh": false, "settable_per_extruder": false }, diff --git a/resources/definitions/ultimaker2_plus.def.json b/resources/definitions/ultimaker2_plus.def.json index bf48353f59..28fd2b71f9 100644 --- a/resources/definitions/ultimaker2_plus.def.json +++ b/resources/definitions/ultimaker2_plus.def.json @@ -15,6 +15,7 @@ "has_machine_materials": true, "has_machine_quality": true, "first_start_actions": [], + "supported_actions": [], "machine_extruder_trains": { "0": "ultimaker2_plus_extruder_0" diff --git a/resources/extruders/cr-x_extruder_0.def.json b/resources/extruders/cr-x_extruder_0.def.json new file mode 100644 index 0000000000..8135815afb --- /dev/null +++ b/resources/extruders/cr-x_extruder_0.def.json @@ -0,0 +1,27 @@ +{ + "id": "cr-x_extruder_0", + "version": 2, + "name": "Left Extruder", + "inherits": "fdmextruder", + "metadata": { + "machine": "Creality CR-X", + "position": "0" + }, + + "overrides": { + "extruder_nr": { + "default_value": 0, + "maximum_value": "1" + }, + "machine_nozzle_offset_x": { "default_value": 0.0 }, + "machine_nozzle_offset_y": { "default_value": 0.0 }, + "material_diameter": { "default_value": 1.75 }, + "machine_nozzle_size": { "default_value": 0.4 }, + "machine_extruder_start_code": { + "default_value": "\nT0 ;switch to extruder 1\nG92 E0 ;reset extruder distance\nG1 F2000 E93 ;load filament\nG92 E0 ;reset extruder distance\nM104 S{material_print_temperature}" + }, + "machine_extruder_end_code": { + "default_value": "\nG92 E0 ;reset extruder distance\nG1 F800 E-5 ;short retract\nG1 F2400 X295 Y265 ;move near prime tower\nG1 F2000 E-93 ;long retract for filament removal\nG92 E0 ;reset extruder distance\nG90" + } + } +} diff --git a/resources/extruders/cr-x_extruder_1.def.json b/resources/extruders/cr-x_extruder_1.def.json new file mode 100644 index 0000000000..9313f2ea78 --- /dev/null +++ b/resources/extruders/cr-x_extruder_1.def.json @@ -0,0 +1,27 @@ +{ + "id": "cr-x_extruder_1", + "version": 2, + "name": "Right Extruder", + "inherits": "fdmextruder", + "metadata": { + "machine": "Creality CR-X", + "position": "1" + }, + + "overrides": { + "extruder_nr": { + "default_value": 1, + "maximum_value": "1" + }, + "machine_nozzle_offset_x": { "default_value": 0.0 }, + "machine_nozzle_offset_y": { "default_value": 0.0 }, + "material_diameter": { "default_value": 1.75 }, + "machine_nozzle_size": { "default_value": 0.4 }, + "machine_extruder_start_code": { + "default_value": "\nT1 ;switch to extruder 2\nG92 E0 ;reset extruder distance\nG1 F2000 E93 ;load filament\nG92 E0 ;reset extruder distance\nM104 S{material_print_temperature}" + }, + "machine_extruder_end_code": { + "default_value": "\nG92 E0 ;reset extruder distance\nG1 F800 E-5 ;short retract\nG1 F2400 X295 Y265 ;move near prime tower\nG1 F2000 E-93 ;long retract for filament removal\nG92 E0 ;reset extruder distance\nG90" + } + } +} diff --git a/resources/meshes/cr-x.stl b/resources/meshes/cr-x.stl new file mode 100644 index 0000000000..4e0668f444 Binary files /dev/null and b/resources/meshes/cr-x.stl differ diff --git a/resources/qml/Account/AccountDetails.qml b/resources/qml/Account/AccountDetails.qml index 45f822e41f..265842e2b4 100644 --- a/resources/qml/Account/AccountDetails.qml +++ b/resources/qml/Account/AccountDetails.qml @@ -1,8 +1,8 @@ // Copyright (c) 2018 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. -import QtQuick 2.7 -import QtQuick.Controls 2.1 +import QtQuick 2.10 +import QtQuick.Controls 2.3 import UM 1.4 as UM import Cura 1.1 as Cura @@ -16,38 +16,6 @@ Column padding: UM.Theme.getSize("wide_margin").height spacing: UM.Theme.getSize("wide_margin").height - AvatarImage - { - id: avatar - width: UM.Theme.getSize("avatar_image").width - height: UM.Theme.getSize("avatar_image").height - anchors.horizontalCenter: parent.horizontalCenter - source: - { - if(loggedIn) - { - if(profileImage) - { - return profileImage - } - return UM.Theme.getImage("avatar_no_user") - } - return UM.Theme.getImage("avatar_no_user") - } - outlineColor: loggedIn ? UM.Theme.getColor("account_widget_outline_active") : UM.Theme.getColor("lining") - } - - Label - { - id: information - anchors.horizontalCenter: parent.horizontalCenter - horizontalAlignment: Text.AlignHCenter - renderType: Text.NativeRendering - 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_bold") : UM.Theme.getFont("default") - color: UM.Theme.getColor("text") - } - Loader { id: accountOperations diff --git a/resources/qml/Account/AccountWidget.qml b/resources/qml/Account/AccountWidget.qml index d3bd6fd130..26b491ce15 100644 --- a/resources/qml/Account/AccountWidget.qml +++ b/resources/qml/Account/AccountWidget.qml @@ -1,46 +1,115 @@ // Copyright (c) 2018 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. -import QtQuick 2.7 -import QtQuick.Controls 2.1 +import QtQuick 2.10 +import QtQuick.Controls 2.3 import UM 1.4 as UM import Cura 1.1 as Cura -Button +Item { - id: accountWidget property var profile: Cura.API.account.userProfile property var loggedIn: Cura.API.account.isLoggedIn - implicitHeight: UM.Theme.getSize("main_window_header").height - implicitWidth: UM.Theme.getSize("main_window_header").height + height: signInButton.height > accountWidget.height ? signInButton.height : accountWidget.height + width: signInButton.width > accountWidget.width ? signInButton.width : accountWidget.width - background: AvatarImage + Button { - id: avatar + id: signInButton - width: Math.round(0.8 * accountWidget.width) - height: Math.round(0.8 * accountWidget.height) - anchors.verticalCenter: accountWidget.verticalCenter - anchors.horizontalCenter: accountWidget.horizontalCenter + anchors.verticalCenter: parent.verticalCenter - source: + text: catalog.i18nc("@action:button", "Sign in") + + height: Math.round(0.5 * UM.Theme.getSize("main_window_header").height) + onClicked: popup.opened ? popup.close() : popup.open() + visible: !loggedIn + + hoverEnabled: true + + background: Rectangle { - if(loggedIn) - { - if(profile["profile_image_url"]) - { - return profile["profile_image_url"] - } - return UM.Theme.getImage("avatar_no_user") - } - return UM.Theme.getImage("avatar_no_user") + radius: UM.Theme.getSize("action_button_radius").width + color: signInButton.hovered ? UM.Theme.getColor("primary_text") : UM.Theme.getColor("main_window_header_background") + border.width: UM.Theme.getSize("default_lining").width + border.color: UM.Theme.getColor("primary_text") + } + + contentItem: Label + { + id: label + text: signInButton.text + font: UM.Theme.getFont("default") + color: signInButton.hovered ? UM.Theme.getColor("main_window_header_background") : UM.Theme.getColor("primary_text") + width: contentWidth + verticalAlignment: Text.AlignVCenter + renderType: Text.NativeRendering } - outlineColor: loggedIn ? UM.Theme.getColor("account_widget_outline_active") : UM.Theme.getColor("lining") } - onClicked: popup.opened ? popup.close() : popup.open() + Button + { + id: accountWidget + + anchors.verticalCenter: parent.verticalCenter + + implicitHeight: UM.Theme.getSize("main_window_header").height + implicitWidth: UM.Theme.getSize("main_window_header").height + + hoverEnabled: true + + visible: loggedIn + + text: (loggedIn && profile["profile_image_url"] == "") ? profile["username"].charAt(0).toUpperCase() : "" + + background: AvatarImage + { + id: avatar + + width: Math.round(0.8 * accountWidget.width) + height: Math.round(0.8 * accountWidget.height) + anchors.verticalCenter: accountWidget.verticalCenter + anchors.horizontalCenter: accountWidget.horizontalCenter + + source: (loggedIn && profile["profile_image_url"]) ? profile["profile_image_url"] : "" + outlineColor: loggedIn ? UM.Theme.getColor("account_widget_outline_active") : UM.Theme.getColor("lining") + } + + contentItem: Item + { + anchors.verticalCenter: accountWidget.verticalCenter + anchors.horizontalCenter: accountWidget.horizontalCenter + visible: avatar.source == "" + Rectangle + { + id: initialCircle + anchors.centerIn: parent + width: Math.min(parent.width, parent.height) + height: width + radius: width + color: accountWidget.hovered ? UM.Theme.getColor("primary_text") : "transparent" + border.width: 1 + border.color: UM.Theme.getColor("primary_text") + } + + Label + { + id: initialLabel + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + text: accountWidget.text + font: UM.Theme.getFont("large_bold") + color: accountWidget.hovered ? UM.Theme.getColor("main_window_header_background") : UM.Theme.getColor("primary_text") + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + renderType: Text.NativeRendering + } + } + + onClicked: popup.opened ? popup.close() : popup.open() + } Popup { diff --git a/resources/qml/Account/AvatarImage.qml b/resources/qml/Account/AvatarImage.qml index b76aff6990..bcbc9f0542 100644 --- a/resources/qml/Account/AvatarImage.qml +++ b/resources/qml/Account/AvatarImage.qml @@ -1,8 +1,8 @@ // Copyright (c) 2018 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. -import QtQuick 2.7 -import QtQuick.Controls 2.1 +import QtQuick 2.10 +import QtQuick.Controls 2.3 import QtGraphicalEffects 1.0 import UM 1.4 as UM @@ -16,6 +16,7 @@ Item property alias source: profileImage.source property alias outlineColor: profileImageOutline.color + property bool hasAvatar: source != "" Image { @@ -32,6 +33,7 @@ Item id: profileImageMask anchors.fill: parent radius: width + color: hasAvatar ? "white" : "transparent" } OpacityMask @@ -39,6 +41,7 @@ Item anchors.fill: parent source: profileImage maskSource: profileImageMask + visible: hasAvatar cached: true } @@ -49,8 +52,9 @@ Item // Make it a bit bigger than it has to, otherwise it sometimes shows a white border. width: parent.width + 2 height: parent.height + 2 + visible: hasAvatar source: UM.Theme.getIcon("circle_outline") sourceSize: Qt.size(parent.width, parent.height) color: UM.Theme.getColor("account_widget_ouline_active") } -} \ No newline at end of file +} diff --git a/resources/qml/Account/GeneralOperations.qml b/resources/qml/Account/GeneralOperations.qml index 666a254cd1..a648a5ad0b 100644 --- a/resources/qml/Account/GeneralOperations.qml +++ b/resources/qml/Account/GeneralOperations.qml @@ -1,31 +1,82 @@ -// Copyright (c) 2018 Ultimaker B.V. +// Copyright (c) 2019 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. -import QtQuick 2.2 -import QtQuick.Controls 1.1 +import QtQuick 2.10 +import QtQuick.Controls 2.3 import UM 1.4 as UM import Cura 1.1 as Cura -Row +Column { spacing: UM.Theme.getSize("default_margin").width + Image + { + id: machinesImage + anchors.horizontalCenter: parent.horizontalCenter + source: UM.Theme.getIcon("sign_in_to_cloud") + horizontalAlignment: Image.AlignHCenter + verticalAlignment: Image.AlignVCenter + } + + Label + { + id: title + anchors.horizontalCenter: parent.horizontalCenter + horizontalAlignment: Text.AlignHCenter + renderType: Text.NativeRendering + text: catalog.i18nc("@label", "Ultimaker Cloud") + font: UM.Theme.getFont("large_bold") + color: UM.Theme.getColor("text") + } + + Label + { + id: generalInformation + anchors.horizontalCenter: parent.horizontalCenter + horizontalAlignment: Text.AlignHCenter + renderType: Text.NativeRendering + text: catalog.i18nc("@label", "The next generation 3D printing workflow") + font: UM.Theme.getFont("default_bold") + color: UM.Theme.getColor("text") + } + + Label + { + id: generalInformationPoints + anchors.horizontalCenter: parent.horizontalCenter + horizontalAlignment: Text.AlignLeft + renderType: Text.NativeRendering + text: catalog.i18nc("@text", "- Send print jobs to Ultimaker printers outside your local network\n- Store your Ultimaker Cura settings in the cloud for use anywhere\n- Get exclusive access to material profiles from leading brands") + lineHeight: 1.4 + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + } + + // placeholder + Label + { + text: " " + } + + Cura.PrimaryButton + { + anchors.horizontalCenter: parent.horizontalCenter + width: UM.Theme.getSize("account_button").width + height: UM.Theme.getSize("account_button").height + text: catalog.i18nc("@button", "Sign in") + onClicked: Cura.API.account.login() + fixedWidthMode: true + } + Cura.SecondaryButton { + anchors.horizontalCenter: parent.horizontalCenter width: UM.Theme.getSize("account_button").width height: UM.Theme.getSize("account_button").height text: catalog.i18nc("@button", "Create account") onClicked: Qt.openUrlExternally(CuraApplication.ultimakerCloudAccountRootUrl + "/app/create") fixedWidthMode: true } - - Cura.PrimaryButton - { - width: UM.Theme.getSize("account_button").width - height: UM.Theme.getSize("account_button").height - text: catalog.i18nc("@button", "Login") - onClicked: Cura.API.account.login() - fixedWidthMode: true - } } \ No newline at end of file diff --git a/resources/qml/Account/UserOperations.qml b/resources/qml/Account/UserOperations.qml index 56c2dc416f..c9fb461696 100644 --- a/resources/qml/Account/UserOperations.qml +++ b/resources/qml/Account/UserOperations.qml @@ -1,31 +1,63 @@ // Copyright (c) 2018 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. -import QtQuick 2.2 -import QtQuick.Controls 1.1 +import QtQuick 2.10 +import QtQuick.Controls 2.3 import UM 1.4 as UM import Cura 1.1 as Cura -Row +Column { + width: Math.max(title.width, + accountButton.width) * 1.5 + spacing: UM.Theme.getSize("default_margin").width + Label + { + id: title + anchors.horizontalCenter: parent.horizontalCenter + horizontalAlignment: Text.AlignHCenter + renderType: Text.NativeRendering + text: catalog.i18nc("@label", "Hi " + profile.username) + font: UM.Theme.getFont("large_bold") + color: UM.Theme.getColor("text") + } + + // placeholder + Label + { + text: " " + } + Cura.SecondaryButton { + id: accountButton + anchors.horizontalCenter: parent.horizontalCenter width: UM.Theme.getSize("account_button").width height: UM.Theme.getSize("account_button").height - text: catalog.i18nc("@button", "Manage account") + text: catalog.i18nc("@button", "Ultimaker account") onClicked: Qt.openUrlExternally(CuraApplication.ultimakerCloudAccountRootUrl) - fixedWidthMode: true + fixedWidthMode: false } - Cura.PrimaryButton + Label { - width: UM.Theme.getSize("account_button").width - height: UM.Theme.getSize("account_button").height - text: catalog.i18nc("@button", "Logout") - onClicked: Cura.API.account.logout() - fixedWidthMode: true + id: signOutButton + anchors.horizontalCenter: parent.horizontalCenter + text: catalog.i18nc("@button", "Sign out") + color: UM.Theme.getColor("secondary_button_text") + font: UM.Theme.getFont("medium") + renderType: Text.NativeRendering + + MouseArea + { + anchors.fill: parent + onClicked: Cura.API.account.logout() + hoverEnabled: true + onEntered: signOutButton.font.underline = true + onExited: signOutButton.font.underline = false + } } -} \ No newline at end of file +} diff --git a/resources/qml/ActionButton.qml b/resources/qml/ActionButton.qml index 6406e83efe..e4e2aedb8a 100644 --- a/resources/qml/ActionButton.qml +++ b/resources/qml/ActionButton.qml @@ -48,12 +48,13 @@ Button contentItem: Row { spacing: UM.Theme.getSize("narrow_margin").width + height: button.height //Left side icon. Only displayed if !isIconOnRightSide. UM.RecolorImage { id: buttonIconLeft source: "" - height: UM.Theme.getSize("action_button_icon").height + height: visible ? UM.Theme.getSize("action_button_icon").height : 0 width: visible ? height : 0 sourceSize.width: width sourceSize.height: height @@ -70,9 +71,11 @@ Button font: UM.Theme.getFont("medium") visible: text != "" renderType: Text.NativeRendering + height: parent.height anchors.verticalCenter: parent.verticalCenter width: fixedWidthMode ? button.width - button.leftPadding - button.rightPadding : undefined horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter elide: Text.ElideRight } @@ -81,7 +84,7 @@ Button { id: buttonIconRight source: buttonIconLeft.source - height: UM.Theme.getSize("action_button_icon").height + height: visible ? UM.Theme.getSize("action_button_icon").height : 0 width: visible ? height : 0 sourceSize.width: width sourceSize.height: height diff --git a/resources/qml/ActionPanel/ActionPanelWidget.qml b/resources/qml/ActionPanel/ActionPanelWidget.qml index 1d9ee95548..4d61380c99 100644 --- a/resources/qml/ActionPanel/ActionPanelWidget.qml +++ b/resources/qml/ActionPanel/ActionPanelWidget.qml @@ -1,4 +1,4 @@ -// Copyright (c) 2018 Ultimaker B.V. +// Copyright (c) 2019 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.7 @@ -12,45 +12,95 @@ import Cura 1.0 as Cura // This element hold all the elements needed for the user to trigger the slicing process, and later // to get information about the printing times, material consumption and the output process (such as // saving to a file, printing over network, ... -Rectangle +Item { - id: actionPanelWidget + id: base + width: childrenRect.width + height: childrenRect.height + visible: CuraApplication.platformActivity - width: UM.Theme.getSize("action_panel_widget").width - height: childrenRect.height + 2 * UM.Theme.getSize("thick_margin").height + property bool hasPreviewButton: true - color: UM.Theme.getColor("main_background") - border.width: UM.Theme.getSize("default_lining").width - border.color: UM.Theme.getColor("lining") - radius: UM.Theme.getSize("default_radius").width - z: 10 - - property bool outputAvailable: UM.Backend.state == UM.Backend.Done || UM.Backend.state == UM.Backend.Disabled - - Loader + Rectangle { - id: loader - anchors + id: actionPanelWidget + + width: UM.Theme.getSize("action_panel_widget").width + height: childrenRect.height + 2 * UM.Theme.getSize("thick_margin").height + anchors. right: parent.right + color: UM.Theme.getColor("main_background") + border.width: UM.Theme.getSize("default_lining").width + border.color: UM.Theme.getColor("lining") + radius: UM.Theme.getSize("default_radius").width + z: 10 + + property bool outputAvailable: UM.Backend.state == UM.Backend.Done || UM.Backend.state == UM.Backend.Disabled + + Loader { - top: parent.top - topMargin: UM.Theme.getSize("thick_margin").height - left: parent.left - leftMargin: UM.Theme.getSize("thick_margin").width - right: parent.right - rightMargin: UM.Theme.getSize("thick_margin").width + id: loader + anchors + { + top: parent.top + topMargin: UM.Theme.getSize("thick_margin").height + left: parent.left + leftMargin: UM.Theme.getSize("thick_margin").width + right: parent.right + rightMargin: UM.Theme.getSize("thick_margin").width + } + sourceComponent: actionPanelWidget.outputAvailable ? outputProcessWidget : sliceProcessWidget + onLoaded: + { + if(actionPanelWidget.outputAvailable) + { + loader.item.hasPreviewButton = base.hasPreviewButton; + } + } + } + + Component + { + id: sliceProcessWidget + SliceProcessWidget { } + } + + Component + { + id: outputProcessWidget + OutputProcessWidget { } } - sourceComponent: outputAvailable ? outputProcessWidget : sliceProcessWidget } - Component + Item { - id: sliceProcessWidget - SliceProcessWidget { } + id: additionalComponents + width: childrenRect.width + anchors.right: actionPanelWidget.left + anchors.rightMargin: UM.Theme.getSize("default_margin").width + anchors.bottom: actionPanelWidget.bottom + anchors.bottomMargin: UM.Theme.getSize("thick_margin").height * 2 + visible: actionPanelWidget.visible + Row + { + id: additionalComponentsRow + anchors.verticalCenter: parent.verticalCenter + spacing: UM.Theme.getSize("default_margin").width + } } - Component + Component.onCompleted: base.addAdditionalComponents() + + Connections { - id: outputProcessWidget - OutputProcessWidget { } + target: CuraApplication + onAdditionalComponentsChanged: base.addAdditionalComponents() + } + + function addAdditionalComponents() + { + for (var component in CuraApplication.additionalComponents["saveButton"]) + { + CuraApplication.additionalComponents["saveButton"][component].parent = additionalComponentsRow + } } } \ No newline at end of file diff --git a/resources/qml/ActionPanel/OutputProcessWidget.qml b/resources/qml/ActionPanel/OutputProcessWidget.qml index 63974d7f34..f4505c620c 100644 --- a/resources/qml/ActionPanel/OutputProcessWidget.qml +++ b/resources/qml/ActionPanel/OutputProcessWidget.qml @@ -1,4 +1,4 @@ -// Copyright (c) 2018 Ultimaker B.V. +// Copyright (c) 2019 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.7 @@ -19,6 +19,7 @@ Column spacing: UM.Theme.getSize("thin_margin").height property bool preSlicedData: PrintInformation.preSliced + property alias hasPreviewButton: previewStageShortcut.visible UM.I18nCatalog { @@ -120,7 +121,6 @@ Column toolTipContentAlignment: Cura.ToolTip.ContentAlignment.AlignLeft onClicked: UM.Controller.setActiveStage("PreviewStage") - visible: UM.Controller.activeStage != null && UM.Controller.activeStage.stageId != "PreviewStage" } Cura.OutputDevicesActionButton diff --git a/resources/qml/ActionPanel/PrintJobInformation.qml b/resources/qml/ActionPanel/PrintJobInformation.qml index 8bd5d5a0d3..7cd466c33f 100644 --- a/resources/qml/ActionPanel/PrintJobInformation.qml +++ b/resources/qml/ActionPanel/PrintJobInformation.qml @@ -1,4 +1,4 @@ -// Copyright (c) 2018 Ultimaker B.V. +// Copyright (c) 2019 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.7 @@ -36,30 +36,63 @@ Column Label { - property var printDuration: PrintInformation.currentPrintTime + id: byLineType - text: + property var printDuration: PrintInformation.currentPrintTime + property var columnWidthMultipliers: [ 0.4, 0.3, 0.3 ] + property var columnHorizontalAligns: [ TextInput.AlignLeft, TextInput.AlignHCenter, TextInput.AlignHCenter ] + + function getMaterialTable() { + var result = [] + // All the time information for the different features is achieved var printTime = PrintInformation.getFeaturePrintTimes() var totalSeconds = parseInt(printDuration.getDisplayString(UM.DurationFormat.Seconds)) // A message is created and displayed when the user hover the time label - var text = "" for(var feature in printTime) { if(!printTime[feature].isTotalDurationZero) { - text += "" + - "".arg(printTime[feature].getDisplayString(UM.DurationFormat.ISO8601).slice(0,-3)) + - "".arg(Math.round(100 * parseInt(printTime[feature].getDisplayString(UM.DurationFormat.Seconds)) / totalSeconds)) + - "" + var row = [] + row.push(feature + ": ") + row.push("%1".arg(printTime[feature].getDisplayString(UM.DurationFormat.ISO8601).slice(0,-3))) + row.push("%1%".arg(Math.round(100 * parseInt(printTime[feature].getDisplayString(UM.DurationFormat.Seconds)) / totalSeconds))) + result.push(row) } } - text += "
" + feature + ":  %1  %1%
" - return text + + return result } + + Column + { + Repeater + { + model: byLineType.getMaterialTable() + Row + { + Repeater + { + model: modelData + Label + { + width: Math.round(byLineType.width * byLineType.columnWidthMultipliers[index]) + height: contentHeight + horizontalAlignment: byLineType.columnHorizontalAligns[index] + font: UM.Theme.getFont("default") + wrapMode: Text.WrapAnywhere + text: modelData + renderType: Text.NativeRendering + } + } + } + } + } + width: parent.width - 2 * UM.Theme.getSize("default_margin").width + height: childrenRect.height color: UM.Theme.getColor("text") font: UM.Theme.getFont("default") renderType: Text.NativeRendering @@ -85,31 +118,19 @@ Column Label { + id: byMaterialType + property var printMaterialLengths: PrintInformation.materialLengths property var printMaterialWeights: PrintInformation.materialWeights property var printMaterialCosts: PrintInformation.materialCosts property var printMaterialNames: PrintInformation.materialNames + property var columnWidthMultipliers: [ 0.4, 0.2, 0.2, 0.2 ] + property var columnHorizontalAligns: [ TextInput.AlignLeft, TextInput.AlignHCenter, TextInput.AlignHCenter, TextInput.AlignHCenter ] - function formatRow(items) + function getMaterialTable() { - var rowHTML = "" - for(var item = 0; item < items.length; item++) - { - if (item == 0) - { - rowHTML += "%1".arg(items[item]) - } - else - { - rowHTML += "  %1".arg(items[item]) - } - } - rowHTML += "" - return rowHTML - } + var result = [] - text: - { var lengths = [] var weights = [] var costs = [] @@ -135,21 +156,46 @@ Column costs = ["0.00"] } - var text = "" for(var index = 0; index < lengths.length; index++) { - text += formatRow([ - "%1:".arg(names[index]), - catalog.i18nc("@label m for meter", "%1m").arg(lengths[index]), - catalog.i18nc("@label g for grams", "%1g").arg(weights[index]), - "%1 %2".arg(UM.Preferences.getValue("cura/currency")).arg(costs[index]), - ]) + var row = [] + row.push("%1".arg(names[index])) + row.push(catalog.i18nc("@label m for meter", "%1m").arg(lengths[index])) + row.push(catalog.i18nc("@label g for grams", "%1g").arg(weights[index])) + row.push("%1 %2".arg(UM.Preferences.getValue("cura/currency")).arg(costs[index])) + result.push(row) } - text += "
" - return text + return result } + + Column + { + Repeater + { + model: byMaterialType.getMaterialTable() + Row + { + Repeater + { + model: modelData + Label + { + width: Math.round(byMaterialType.width * byMaterialType.columnWidthMultipliers[index]) + height: contentHeight + horizontalAlignment: byLineType.columnHorizontalAligns[index] + font: UM.Theme.getFont("default") + wrapMode: Text.WrapAnywhere + text: modelData + renderType: Text.NativeRendering + } + } + } + } + } + width: parent.width - 2 * UM.Theme.getSize("default_margin").width + height: childrenRect.height color: UM.Theme.getColor("text") font: UM.Theme.getFont("default") renderType: Text.NativeRendering diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index a522e3ffa0..44ff31ef31 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -246,63 +246,6 @@ UM.MainWindow } } - Cura.ActionPanelWidget - { - id: actionPanelWidget - anchors.right: parent.right - anchors.bottom: parent.bottom - anchors.rightMargin: UM.Theme.getSize("thick_margin").width - anchors.bottomMargin: UM.Theme.getSize("thick_margin").height - - /* - Show this panel only if there is something on the build plate, and there is NOT an opaque item in front of the build plate. - This cannot be solved by Z indexing! If you want to try solving this, please increase this counter when you're done: - Number of people having tried to fix this by z-indexing: 2 - The problem arises from the following render order requirements: - - The stage menu must be rendered above the stage main. - - The stage main must be rendered above the action panel (because the monitor page must be rendered above the action panel). - - The action panel must be rendered above the expandable components drop-down. - However since the expandable components drop-downs are child elements of the stage menu, - they can't be rendered lower than elements that are lower than the stage menu. - Therefore we opted to forego the second requirement and hide the action panel instead when something obscures it (except the expandable components). - We assume that QQuickRectangles are always opaque and any other item is not. - */ - visible: CuraApplication.platformActivity && (main.item == null || !qmlTypeOf(main.item, "QQuickRectangle")) - } - - Item - { - id: additionalComponents - width: childrenRect.width - anchors.right: actionPanelWidget.left - anchors.rightMargin: UM.Theme.getSize("default_margin").width - anchors.bottom: actionPanelWidget.bottom - anchors.bottomMargin: UM.Theme.getSize("thick_margin").height * 2 - visible: actionPanelWidget.visible - Row - { - id: additionalComponentsRow - anchors.verticalCenter: parent.verticalCenter - spacing: UM.Theme.getSize("default_margin").width - } - } - - Component.onCompleted: contentItem.addAdditionalComponents() - - Connections - { - target: CuraApplication - onAdditionalComponentsChanged: contentItem.addAdditionalComponents("saveButton") - } - - function addAdditionalComponents() - { - for (var component in CuraApplication.additionalComponents["saveButton"]) - { - CuraApplication.additionalComponents["saveButton"][component].parent = additionalComponentsRow - } - } - Loader { // A stage can control this area. If nothing is set, it will therefore show the 3D view. @@ -373,6 +316,24 @@ UM.MainWindow bottom: parent.bottom bottomMargin: UM.Theme.getSize("default_margin").height } + + primaryButton: Component + { + Cura.PrimaryButton + { + text: model.name + height: UM.Theme.getSize("message_action_button").height + } + } + + secondaryButton: Component + { + Cura.SecondaryButton + { + text: model.name + height: UM.Theme.getSize("message_action_button").height + } + } } } diff --git a/resources/qml/Dialogs/AboutDialog.qml b/resources/qml/Dialogs/AboutDialog.qml index ac115a0e5f..9a92bcbe1f 100644 --- a/resources/qml/Dialogs/AboutDialog.qml +++ b/resources/qml/Dialogs/AboutDialog.qml @@ -35,9 +35,9 @@ UM.Dialog { id: logo width: (base.minimumWidth * 0.85) | 0 - height: (width * (UM.Theme.getSize("logo").height / UM.Theme.getSize("logo").width)) | 0 - - source: UM.Theme.getImage("logo_about") + source: UM.Theme.getImage("logo") + sourceSize.width: width + sourceSize.height: height anchors.top: parent.top anchors.topMargin: ((base.minimumWidth - width) / 2) | 0 diff --git a/resources/qml/MainWindow/MainWindowHeader.qml b/resources/qml/MainWindow/MainWindowHeader.qml index 3e296ead40..ffcad4c75b 100644 --- a/resources/qml/MainWindow/MainWindowHeader.qml +++ b/resources/qml/MainWindow/MainWindowHeader.qml @@ -29,7 +29,7 @@ Item source: UM.Theme.getImage("logo") width: UM.Theme.getSize("logo").width height: UM.Theme.getSize("logo").height - + fillMode: Image.PreserveAspectFit sourceSize.width: width sourceSize.height: height } @@ -122,6 +122,7 @@ Item id: accountWidget anchors { + verticalCenter: parent.verticalCenter right: parent.right rightMargin: UM.Theme.getSize("default_margin").width } diff --git a/resources/qml/Menus/ConfigurationMenu/ConfigurationItem.qml b/resources/qml/Menus/ConfigurationMenu/ConfigurationItem.qml index 058c1ff4c2..296ae62366 100644 --- a/resources/qml/Menus/ConfigurationMenu/ConfigurationItem.qml +++ b/resources/qml/Menus/ConfigurationMenu/ConfigurationItem.qml @@ -218,7 +218,8 @@ Button { if(isValidMaterial) { - Cura.MachineManager.applyRemoteConfiguration(configuration); + toggleContent() + Cura.MachineManager.applyRemoteConfiguration(configuration) } } } diff --git a/resources/qml/Menus/ConfigurationMenu/CustomConfiguration.qml b/resources/qml/Menus/ConfigurationMenu/CustomConfiguration.qml index 5cecda4e5c..663e2497ed 100644 --- a/resources/qml/Menus/ConfigurationMenu/CustomConfiguration.qml +++ b/resources/qml/Menus/ConfigurationMenu/CustomConfiguration.qml @@ -1,4 +1,4 @@ -// Copyright (c) 2018 Ultimaker B.V. +// Copyright (c) 2019 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.6 @@ -288,6 +288,58 @@ Item menu: Cura.NozzleMenu { extruderIndex: Cura.ExtruderManager.activeExtruderIndex } } } + + Row + { + id: warnings + height: UM.Theme.getSize("print_setup_big_item").height + visible: buildplateCompatibilityError || buildplateCompatibilityWarning + + property bool buildplateCompatibilityError: !Cura.MachineManager.variantBuildplateCompatible && !Cura.MachineManager.variantBuildplateUsable + property bool buildplateCompatibilityWarning: Cura.MachineManager.variantBuildplateUsable + + // This is a space holder aligning the warning messages. + Label + { + text: "" + width: selectors.textWidth + renderType: Text.NativeRendering + } + + Item + { + width: selectors.controlWidth + height: parent.height + + UM.RecolorImage + { + id: warningImage + anchors.left: parent.left + source: UM.Theme.getIcon("warning") + width: UM.Theme.getSize("section_icon").width + height: UM.Theme.getSize("section_icon").height + sourceSize.width: width + sourceSize.height: height + color: UM.Theme.getColor("material_compatibility_warning") + visible: !Cura.MachineManager.isCurrentSetupSupported || warnings.buildplateCompatibilityError || warnings.buildplateCompatibilityWarning + } + + Label + { + id: materialCompatibilityLabel + anchors.left: warningImage.right + anchors.leftMargin: UM.Theme.getSize("default_margin").width + verticalAlignment: Text.AlignVCenter + width: selectors.controlWidth - warningImage.width - UM.Theme.getSize("default_margin").width + text: catalog.i18nc("@label", "Use glue for better adhesion with this material combination.") + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + visible: CuraSDKVersion == "dev" ? false : warnings.buildplateCompatibilityError || warnings.buildplateCompatibilityWarning + wrapMode: Text.WordWrap + renderType: Text.NativeRendering + } + } + } } } } diff --git a/resources/qml/Menus/NetworkPrinterMenu.qml b/resources/qml/Menus/NetworkPrinterMenu.qml index 41f3054e92..845159f3b2 100644 --- a/resources/qml/Menus/NetworkPrinterMenu.qml +++ b/resources/qml/Menus/NetworkPrinterMenu.qml @@ -14,9 +14,9 @@ Instantiator { property string connectGroupName: { - if("connect_group_name" in model.metadata) + if("group_name" in model.metadata) { - return model.metadata["connect_group_name"] + return model.metadata["group_name"] } return "" } diff --git a/resources/qml/Preferences/MachinesPage.qml b/resources/qml/Preferences/MachinesPage.qml index f9c1a9b0a0..6f214a7efb 100644 --- a/resources/qml/Preferences/MachinesPage.qml +++ b/resources/qml/Preferences/MachinesPage.qml @@ -56,7 +56,7 @@ UM.ManagementPage { text: catalog.i18nc("@action:button", "Rename"); iconName: "edit-rename"; - enabled: base.currentItem != null && base.currentItem.metadata.connect_group_name == null + enabled: base.currentItem != null && base.currentItem.metadata.group_name == null onClicked: renameDialog.open(); } ] diff --git a/resources/qml/Preferences/ProfilesPage.qml b/resources/qml/Preferences/ProfilesPage.qml index f23a04d800..52c69b780e 100644 --- a/resources/qml/Preferences/ProfilesPage.qml +++ b/resources/qml/Preferences/ProfilesPage.qml @@ -173,6 +173,7 @@ Item id: createQualityDialog title: catalog.i18nc("@title:window", "Create Profile") object: "" + explanation: catalog.i18nc("@info", "Please provide a name for this profile.") onAccepted: { base.newQualityNameToSelect = newName; // We want to switch to the new profile once it's created diff --git a/resources/qml/PrinterSelector/MachineSelectorList.qml b/resources/qml/PrinterSelector/MachineSelectorList.qml index 5fd3515cd3..49d9d31f2b 100644 --- a/resources/qml/PrinterSelector/MachineSelectorList.qml +++ b/resources/qml/PrinterSelector/MachineSelectorList.qml @@ -38,7 +38,7 @@ ListView var result = Cura.MachineManager.activeMachineId == model.id if (Cura.MachineManager.activeMachineHasRemoteConnection) { - result |= Cura.MachineManager.activeMachineNetworkGroupName == model.metadata["connect_group_name"] + result |= Cura.MachineManager.activeMachineNetworkGroupName == model.metadata["group_name"] } return result } diff --git a/resources/quality/nwa3d_a5/nwa3d_a5_best.inst.cfg b/resources/quality/nwa3d_a5/nwa3d_a5_best.inst.cfg index 933f731dbd..1c1fbfe0fb 100644 --- a/resources/quality/nwa3d_a5/nwa3d_a5_best.inst.cfg +++ b/resources/quality/nwa3d_a5/nwa3d_a5_best.inst.cfg @@ -4,7 +4,7 @@ name = Best Quality definition = nwa3d_a5 [metadata] -setting_version = 6 +setting_version = 7 type = quality quality_type = best weight = 1 diff --git a/resources/quality/nwa3d_a5/nwa3d_a5_fast.inst.cfg b/resources/quality/nwa3d_a5/nwa3d_a5_fast.inst.cfg index 167bfb5dad..99bb49cc98 100644 --- a/resources/quality/nwa3d_a5/nwa3d_a5_fast.inst.cfg +++ b/resources/quality/nwa3d_a5/nwa3d_a5_fast.inst.cfg @@ -4,7 +4,7 @@ name = Fast Quality definition = nwa3d_a5 [metadata] -setting_version = 6 +setting_version = 7 type = quality quality_type = fast weight = -1 diff --git a/resources/quality/nwa3d_a5/nwa3d_a5_normal.inst.cfg b/resources/quality/nwa3d_a5/nwa3d_a5_normal.inst.cfg index 30bcbed244..3ba9e0a96f 100644 --- a/resources/quality/nwa3d_a5/nwa3d_a5_normal.inst.cfg +++ b/resources/quality/nwa3d_a5/nwa3d_a5_normal.inst.cfg @@ -4,7 +4,7 @@ name = Normal Quality definition = nwa3d_a5 [metadata] -setting_version = 6 +setting_version = 7 type = quality quality_type = normal weight = 0 diff --git a/resources/themes/cura-dark/theme.json b/resources/themes/cura-dark/theme.json index 6b29073475..41033e7e75 100644 --- a/resources/themes/cura-dark/theme.json +++ b/resources/themes/cura-dark/theme.json @@ -6,10 +6,11 @@ "colors": { "main_background": [39, 44, 48, 255], + "message_background": [39, 44, 48, 255], "wide_lining": [31, 36, 39, 255], "thick_lining": [255, 255, 255, 30], "lining": [64, 69, 72, 255], - "viewport_overlay": [0, 6, 9, 222], + "viewport_overlay": [30, 36, 39, 255], "primary": [12, 169, 227, 255], "primary_hover": [48, 182, 231, 255], @@ -215,24 +216,40 @@ "toolbox_header_button_text_inactive": [128, 128, 128, 255], "toolbox_header_button_text_hovered": [255, 255, 255, 255], - "monitor_card_background_inactive": [43, 48, 52, 255], - "monitor_card_background": [43, 48, 52, 255], - "monitor_context_menu_background": [80, 84, 87, 255], - "monitor_context_menu_dots": [0, 167, 233, 255], - "monitor_context_menu_highlight": [0, 167, 233, 255], - "monitor_image_overlay": [255, 255, 255, 255], - "monitor_lining_heavy": [255, 255, 255, 255], - "monitor_lining_light": [102, 102, 102, 255], - "monitor_pill_background": [102, 102, 102, 255], + "monitor_printer_family_tag": [86, 86, 106, 255], + "monitor_text_primary": [229, 229, 229, 255], + "monitor_text_disabled": [102, 102, 102, 255], + "monitor_text_link": [103, 160, 252, 255], + "monitor_icon_primary": [229, 229, 229, 255], + "monitor_icon_accent": [51, 53, 54, 255], + + "monitor_secondary_button_hover": [80, 80, 80, 255], + "monitor_secondary_button": [92, 92, 92, 255], + "monitor_secondary_button_text": [250, 250, 250, 255], + "monitor_secondary_button_shadow": [74, 74, 74, 255], + + "monitor_card_border": [102, 102, 102, 255], + "monitor_card_background": [51, 53, 54, 255], + "monitor_card_hover": [84, 89, 95, 255], + + "monitor_stage_background": [30, 36, 39, 255], + "monitor_stage_background_fade": [30, 36, 39, 102], + + "monitor_progress_bar_fill": [50, 130, 255, 255], + "monitor_progress_bar_deactive": [102, 102, 102, 255], + "monitor_progress_bar_empty": [67, 67, 67, 255], + + "monitor_tooltip": [25, 25, 25, 255], + "monitor_tooltip_text": [229, 229, 229, 255], + "monitor_context_menu": [67, 67, 67, 255], + "monitor_context_menu_hover": [30, 102, 215, 255], + + "monitor_skeleton_loading": [102, 102, 102, 255], "monitor_placeholder_image": [102, 102, 102, 255], - "monitor_printer_icon": [255, 255, 255, 255], - "monitor_progress_background_text": [102, 102, 102, 255], - "monitor_progress_background": [80, 84, 87, 255], - "monitor_progress_fill_inactive": [216, 216, 216, 255], - "monitor_progress_fill_text": [0, 0, 0, 255], - "monitor_progress_fill": [216, 216, 216, 255], - "monotir_printer_icon_inactive": [154, 154, 154, 255], - "monitor_skeleton_fill": [31, 36, 39, 255], - "monitor_skeleton_fill_dark": [31, 36, 39, 255] + "monitor_image_overlay": [0, 0, 0, 255], + "monitor_shadow": [4, 10, 13, 255], + + "monitor_carousel_dot": [119, 119, 119, 255], + "monitor_carousel_dot_current": [216, 216, 216, 255] } } diff --git a/resources/themes/cura-light/icons/sign_in_to_cloud.svg b/resources/themes/cura-light/icons/sign_in_to_cloud.svg new file mode 100644 index 0000000000..75abb176c2 --- /dev/null +++ b/resources/themes/cura-light/icons/sign_in_to_cloud.svg @@ -0,0 +1,36 @@ + + + + Group-cloud + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/themes/cura-light/images/avatar_no_user.svg b/resources/themes/cura-light/images/avatar_no_user.svg deleted file mode 100644 index bef9cb52db..0000000000 --- a/resources/themes/cura-light/images/avatar_no_user.svg +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/resources/themes/cura-light/images/logo.svg b/resources/themes/cura-light/images/logo.svg index 55842ef530..814b157e2a 100644 --- a/resources/themes/cura-light/images/logo.svg +++ b/resources/themes/cura-light/images/logo.svg @@ -1,6 +1,4 @@ - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="svg12" + sodipodi:docname="logo2.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + + + + + diff --git a/resources/themes/cura-light/images/logo_about.svg b/resources/themes/cura-light/images/logo_about.svg deleted file mode 100644 index 34301fd6c9..0000000000 --- a/resources/themes/cura-light/images/logo_about.svg +++ /dev/null @@ -1,172 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index 6e76080c3e..7a39c8f418 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -146,7 +146,7 @@ "wide_lining": [245, 245, 245, 255], "thick_lining": [127, 127, 127, 255], "lining": [192, 193, 194, 255], - "viewport_overlay": [0, 0, 0, 192], + "viewport_overlay": [246, 246, 246, 255], "primary": [50, 130, 255, 255], "primary_shadow": [64, 47, 205, 255], @@ -294,7 +294,7 @@ "setting_validation_ok": [255, 255, 255, 255], "setting_filter_field" : [153, 153, 153, 255], - "material_compatibility_warning": [0, 0, 0, 255], + "material_compatibility_warning": [243, 166, 59, 255], "progressbar_background": [245, 245, 245, 255], "progressbar_control": [50, 130, 255, 255], @@ -320,17 +320,9 @@ "tooltip_text": [255, 255, 255, 255], "message_background": [255, 255, 255, 255], - "message_shadow": [0, 0, 0, 120], "message_border": [192, 193, 194, 255], - "message_text": [0, 0, 0, 255], "message_close": [102, 102, 102, 255], "message_close_hover": [8, 7, 63, 255], - "message_button": [38, 113, 231, 255], - "message_button_hover": [81, 145, 247, 255], - "message_button_active": [38, 113, 231, 255], - "message_button_text": [255, 255, 255, 255], - "message_button_text_hover": [255, 255, 255, 255], - "message_button_text_active": [255, 255, 255, 255], "message_progressbar_background": [245, 245, 245, 255], "message_progressbar_control": [50, 130, 255, 255], @@ -400,27 +392,41 @@ "favorites_header_text_hover": [31, 36, 39, 255], "favorites_row_selected": [196, 239, 255, 255], - "monitor_card_background_inactive": [240, 240, 240, 255], + "monitor_printer_family_tag": [228, 228, 242, 255], + "monitor_text_primary": [65, 64, 84, 255], + "monitor_text_disabled": [238, 238, 238, 255], + "monitor_text_link": [50, 130, 255, 255], + "monitor_icon_primary": [10, 8, 80, 255], + "monitor_icon_accent": [255, 255, 255, 255], + + "monitor_secondary_button_hover": [228, 228, 228, 255], + "monitor_secondary_button": [240, 240, 240, 255], + "monitor_secondary_button_text": [30, 102, 215, 255], + "monitor_secondary_button_shadow": [216, 216, 216, 255], + + "monitor_card_border": [192, 193, 194, 255], "monitor_card_background": [255, 255, 255, 255], - "monitor_context_menu_background": [255, 255, 255, 255], - "monitor_context_menu_dots": [154, 154, 154, 255], - "monitor_context_menu_highlight": [245, 245, 245, 255], - "monitor_image_overlay": [0, 0, 0, 255], - "monitor_lining_heavy": [0, 0, 0, 255], - "monitor_lining_light": [230, 230, 230, 255], - "monitor_pill_background": [245, 245, 245, 255], + "monitor_card_hover": [232, 242, 252, 255], + + "monitor_stage_background": [246, 246, 246, 255], + "monitor_stage_background_fade": [246, 246, 246, 102], + + "monitor_progress_bar_fill": [50, 130, 255, 255], + "monitor_progress_bar_deactive": [192, 193, 194, 255], + "monitor_progress_bar_empty": [245, 245, 245, 255], + + "monitor_tooltip": [25, 25, 25, 255], + "monitor_tooltip_text": [255, 255, 255, 255], + "monitor_context_menu": [255, 255, 255, 255], + "monitor_context_menu_hover": [245, 245, 245, 255], + + "monitor_skeleton_loading": [238, 238, 238, 255], "monitor_placeholder_image": [230, 230, 230, 255], - "monitor_printer_icon_inactive": [154, 154, 154, 255], - "monitor_printer_icon": [50, 130, 255, 255], - "monitor_progress_background_text": [0,0,0,255], - "monitor_progress_background": [245, 245, 245, 255], - "monitor_progress_fill_inactive": [154, 154, 154, 255], - "monitor_progress_fill_text": [255,255,255,255], - "monitor_progress_fill": [50, 130, 255, 255], - "monitor_shadow": [0, 0, 0, 63], - "monitor_skeleton_fill": [245, 245, 245, 255], - "monitor_skeleton_fill_dark": [216, 216, 216, 255], - "monitor_text_inactive": [154, 154, 154, 255] + "monitor_image_overlay": [0, 0, 0, 255], + "monitor_shadow": [220, 220, 220, 255], + + "monitor_carousel_dot": [216, 216, 216, 255], + "monitor_carousel_dot_current": [119, 119, 119, 255] }, "sizes": { @@ -432,7 +438,7 @@ "stage_menu": [0.0, 4.0], - "account_button": [12, 3], + "account_button": [12, 2.5], "print_setup_widget": [38.0, 30.0], "print_setup_mode_toggle": [0.0, 2.0], @@ -499,7 +505,7 @@ "button_icon": [2.5, 2.5], "button_lining": [0, 0], - "action_button": [15.0, 3.0], + "action_button": [15.0, 2.5], "action_button_icon": [1.0, 1.0], "action_button_radius": [0.15, 0.15], @@ -555,12 +561,8 @@ "message": [30.0, 5.0], "message_close": [1, 1], - "message_button": [6.0, 1.8], - "message_shadow": [0, 0], - "message_margin": [0, 1.0], - "message_inner_margin": [1.5, 1.5], "message_radius": [0.25, 0.25], - "message_button_radius": [0.15, 0.15], + "message_action_button": [0, 2.0], "infill_button_margin": [0.5, 0.5], @@ -596,10 +598,14 @@ "monitor_config_override_box": [1.0, 14.0], "monitor_extruder_circle": [2.75, 2.75], - "monitor_text_line": [1.16, 1.16], + "monitor_text_line": [1.5, 1.5], + "monitor_text_line_large": [2.33, 2.33], "monitor_thick_lining": [0.16, 0.16], "monitor_corner_radius": [0.3, 0.3], "monitor_shadow_radius": [0.4, 0.4], - "monitor_shadow_offset": [0.15, 0.15] + "monitor_shadow_offset": [0.15, 0.15], + "monitor_empty_state_offset": [5.6, 5.6], + "monitor_empty_state_size": [35.0, 25.0], + "monitor_external_link_icon": [1.16, 1.16] } } diff --git a/tests/TestGCodeListDecorator.py b/tests/TestGCodeListDecorator.py new file mode 100644 index 0000000000..058aa132f9 --- /dev/null +++ b/tests/TestGCodeListDecorator.py @@ -0,0 +1,16 @@ +from cura.Scene.GCodeListDecorator import GCodeListDecorator + + +def test_setAndGetList(): + decorator = GCodeListDecorator() + + decorator.setGCodeList(["Test"]) + assert decorator.getGCodeList() == ["Test"] + + +def test_copyGCodeDecorator(): + decorator = GCodeListDecorator() + decorator.setGCodeList(["Test"]) + import copy + copied_decorator = copy.deepcopy(decorator) + assert decorator.getGCodeList() == copied_decorator.getGCodeList() diff --git a/tests/TestMachineAction.py b/tests/TestMachineAction.py index 5d1805b707..f1487a1d9f 100755 --- a/tests/TestMachineAction.py +++ b/tests/TestMachineAction.py @@ -104,3 +104,18 @@ def test_addMachineAction(machine_action_manager): machine_action_manager.addFirstStartAction(test_machine, "test_action") machine_action_manager.addFirstStartAction(test_machine, "test_action") assert machine_action_manager.getFirstStartActions(test_machine) == [test_action, test_action] + + # Adding unknown action should not crash. + machine_action_manager.addFirstStartAction(test_machine, "key_that_doesnt_exists") + +def test_removeMachineAction(machine_action_manager): + test_action = MachineAction(key="test_action") + test_machine = Machine("test_machine") + machine_action_manager.addMachineAction(test_action) + + # Remove the machine action + machine_action_manager.removeMachineAction(test_action) + assert machine_action_manager.getMachineAction("test_action") is None + + # Attempting to remove it again should not crash. + machine_action_manager.removeMachineAction(test_action)