diff --git a/cura/CuraActions.py b/cura/CuraActions.py index 93a18318df..49f7e740a9 100644 --- a/cura/CuraActions.py +++ b/cura/CuraActions.py @@ -3,7 +3,7 @@ from PyQt5.QtCore import QObject, QUrl from PyQt5.QtGui import QDesktopServices -from typing import List, TYPE_CHECKING +from typing import List, TYPE_CHECKING, cast from UM.Event import CallFunctionEvent from UM.FlameProfiler import pyqtSlot @@ -61,8 +61,10 @@ class CuraActions(QObject): operation = GroupedOperation() for node in Selection.getAllSelectedObjects(): current_node = node - while current_node.getParent() and current_node.getParent().callDecoration("isGroup"): - current_node = current_node.getParent() + parent_node = current_node.getParent() + while parent_node and parent_node.callDecoration("isGroup"): + current_node = parent_node + parent_node = current_node.getParent() # This was formerly done with SetTransformOperation but because of # unpredictable matrix deconstruction it was possible that mirrors @@ -150,13 +152,13 @@ class CuraActions(QObject): root = cura.CuraApplication.CuraApplication.getInstance().getController().getScene().getRoot() - nodes_to_change = [] + nodes_to_change = [] # type: List[SceneNode] for node in Selection.getAllSelectedObjects(): parent_node = node # Find the parent node to change instead while parent_node.getParent() != root: - parent_node = parent_node.getParent() + parent_node = cast(SceneNode, parent_node.getParent()) - for single_node in BreadthFirstIterator(parent_node): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. + for single_node in BreadthFirstIterator(parent_node): # type: ignore #Ignore type error because iter() should get called automatically by Python syntax. nodes_to_change.append(single_node) if not nodes_to_change: diff --git a/cura/Machines/Models/QualityProfilesDropDownMenuModel.py b/cura/Machines/Models/QualityProfilesDropDownMenuModel.py index a01cc1194f..747882b041 100644 --- a/cura/Machines/Models/QualityProfilesDropDownMenuModel.py +++ b/cura/Machines/Models/QualityProfilesDropDownMenuModel.py @@ -21,6 +21,7 @@ class QualityProfilesDropDownMenuModel(ListModel): AvailableRole = Qt.UserRole + 5 QualityGroupRole = Qt.UserRole + 6 QualityChangesGroupRole = Qt.UserRole + 7 + IsExperimentalRole = Qt.UserRole + 8 def __init__(self, parent = None): super().__init__(parent) @@ -32,6 +33,7 @@ class QualityProfilesDropDownMenuModel(ListModel): self.addRoleName(self.AvailableRole, "available") #Whether the quality profile is available in our current nozzle + material. self.addRoleName(self.QualityGroupRole, "quality_group") self.addRoleName(self.QualityChangesGroupRole, "quality_changes_group") + self.addRoleName(self.IsExperimentalRole, "is_experimental") self._application = Application.getInstance() self._machine_manager = self._application.getMachineManager() @@ -74,7 +76,8 @@ class QualityProfilesDropDownMenuModel(ListModel): "layer_height": layer_height, "layer_height_unit": self._layer_height_unit, "available": quality_group.is_available, - "quality_group": quality_group} + "quality_group": quality_group, + "is_experimental": quality_group.is_experimental} item_list.append(item) diff --git a/cura/Machines/QualityGroup.py b/cura/Machines/QualityGroup.py index 535ba453f8..f5bcbb0de8 100644 --- a/cura/Machines/QualityGroup.py +++ b/cura/Machines/QualityGroup.py @@ -4,6 +4,9 @@ from typing import Dict, Optional, List, Set from PyQt5.QtCore import QObject, pyqtSlot + +from UM.Util import parseBool + from cura.Machines.ContainerNode import ContainerNode @@ -29,6 +32,7 @@ class QualityGroup(QObject): self.nodes_for_extruders = {} # type: Dict[int, ContainerNode] self.quality_type = quality_type self.is_available = False + self.is_experimental = False @pyqtSlot(result = str) def getName(self) -> str: @@ -51,3 +55,17 @@ class QualityGroup(QObject): for extruder_node in self.nodes_for_extruders.values(): result.append(extruder_node) return result + + def setGlobalNode(self, node: "ContainerNode") -> None: + self.node_for_global = node + + # Update is_experimental flag + is_experimental = parseBool(node.getMetaDataEntry("is_experimental", False)) + self.is_experimental |= is_experimental + + def setExtruderNode(self, position: int, node: "ContainerNode") -> None: + self.nodes_for_extruders[position] = node + + # Update is_experimental flag + is_experimental = parseBool(node.getMetaDataEntry("is_experimental", False)) + self.is_experimental |= is_experimental diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py index a784d17f0b..34cc9ce4b2 100644 --- a/cura/Machines/QualityManager.py +++ b/cura/Machines/QualityManager.py @@ -235,7 +235,7 @@ class QualityManager(QObject): for quality_type, quality_node in node.quality_type_map.items(): quality_group = QualityGroup(quality_node.getMetaDataEntry("name", ""), quality_type) - quality_group.node_for_global = quality_node + quality_group.setGlobalNode(quality_node) quality_group_dict[quality_type] = quality_group break @@ -337,7 +337,7 @@ class QualityManager(QObject): quality_group = quality_group_dict[quality_type] if position not in quality_group.nodes_for_extruders: - quality_group.nodes_for_extruders[position] = quality_node + quality_group.setExtruderNode(position, quality_node) # If the machine has its own specific qualities, for extruders, it should skip the global qualities # and use the material/variant specific qualities. @@ -367,7 +367,7 @@ class QualityManager(QObject): if node and node.quality_type_map: for quality_type, quality_node in node.quality_type_map.items(): quality_group = QualityGroup(quality_node.getMetaDataEntry("name", ""), quality_type) - quality_group.node_for_global = quality_node + quality_group.setGlobalNode(quality_node) quality_group_dict[quality_type] = quality_group break diff --git a/cura/Machines/VariantManager.py b/cura/Machines/VariantManager.py index f6feb70e09..eaaa9fc5f0 100644 --- a/cura/Machines/VariantManager.py +++ b/cura/Machines/VariantManager.py @@ -107,7 +107,7 @@ class VariantManager: break return variant_node - return self._machine_to_variant_dict_map[machine_definition_id].get(variant_type, {}).get(variant_name) + return self._machine_to_variant_dict_map.get(machine_definition_id, {}).get(variant_type, {}).get(variant_name) def getVariantNodes(self, machine: "GlobalStack", variant_type: "VariantType") -> Dict[str, ContainerNode]: machine_definition_id = machine.definition.getId() diff --git a/cura/PrintInformation.py b/cura/PrintInformation.py index e11f70a54c..22c3eb1734 100644 --- a/cura/PrintInformation.py +++ b/cura/PrintInformation.py @@ -14,8 +14,7 @@ from UM.Logger import Logger from UM.Qt.Duration import Duration from UM.Scene.SceneNode import SceneNode from UM.i18n import i18nCatalog -from UM.MimeTypeDatabase import MimeTypeDatabase - +from UM.MimeTypeDatabase import MimeTypeDatabase, MimeTypeNotFoundError from typing import TYPE_CHECKING @@ -361,7 +360,7 @@ class PrintInformation(QObject): try: mime_type = MimeTypeDatabase.getMimeTypeForFile(name) data = mime_type.stripExtension(name) - except: + except MimeTypeNotFoundError: Logger.log("w", "Unsupported Mime Type Database file extension %s", name) if data is not None and check_name is not None: @@ -416,7 +415,7 @@ class PrintInformation(QObject): return ''.join(char for char in unicodedata.normalize('NFD', to_strip) if unicodedata.category(char) != 'Mn') @pyqtSlot(result = "QVariantMap") - def getFeaturePrintTimes(self): + def getFeaturePrintTimes(self) -> Dict[str, Duration]: result = {} if self._active_build_plate not in self._print_times_per_feature: self._initPrintTimesPerFeature(self._active_build_plate) diff --git a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py index 35d2ce014a..9a3be936a2 100644 --- a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py +++ b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py @@ -147,6 +147,9 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice): request.setHeader(QNetworkRequest.UserAgentHeader, self._user_agent) return request + def createFormPart(self, content_header: str, data: bytes, content_type: Optional[str] = None) -> QHttpPart: + return self._createFormPart(content_header, data, content_type) + def _createFormPart(self, content_header: str, data: bytes, content_type: Optional[str] = None) -> QHttpPart: part = QHttpPart() diff --git a/cura/PrinterOutput/PrintJobOutputModel.py b/cura/PrinterOutput/PrintJobOutputModel.py index 25b168e6fd..256c9dffe9 100644 --- a/cura/PrinterOutput/PrintJobOutputModel.py +++ b/cura/PrinterOutput/PrintJobOutputModel.py @@ -118,17 +118,40 @@ class PrintJobOutputModel(QObject): self.nameChanged.emit() @pyqtProperty(int, notify = timeTotalChanged) - def timeTotal(self): + def timeTotal(self) -> int: return self._time_total @pyqtProperty(int, notify = timeElapsedChanged) - def timeElapsed(self): + def timeElapsed(self) -> int: return self._time_elapsed + @pyqtProperty(int, notify = timeElapsedChanged) + def timeRemaining(self) -> int: + # Never get a negative time remaining + return max(self.timeTotal - self.timeElapsed, 0) + + @pyqtProperty(float, notify = timeElapsedChanged) + def progress(self) -> float: + result = self.timeElapsed / self.timeTotal + # Never get a progress past 1.0 + return min(result, 1.0) + @pyqtProperty(str, notify=stateChanged) - def state(self): + def state(self) -> str: return self._state + @pyqtProperty(bool, notify=stateChanged) + def isActive(self) -> bool: + inactiveStates = [ + "pausing", + "paused", + "resuming", + "wait_cleanup" + ] + if self.state in inactiveStates and self.timeRemaining > 0: + return False + return True + def updateTimeTotal(self, new_time_total): if self._time_total != new_time_total: self._time_total = new_time_total diff --git a/cura/Scene/ConvexHullNode.py b/cura/Scene/ConvexHullNode.py index 4c79c7d5dc..886ed93ad3 100644 --- a/cura/Scene/ConvexHullNode.py +++ b/cura/Scene/ConvexHullNode.py @@ -1,7 +1,10 @@ # Copyright (c) 2015 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +from typing import Optional from UM.Application import Application +from UM.Math.Polygon import Polygon +from UM.Qt.QtApplication import QtApplication from UM.Scene.SceneNode import SceneNode from UM.Resources import Resources from UM.Math.Color import Color @@ -16,7 +19,7 @@ class ConvexHullNode(SceneNode): # location an object uses on the buildplate. This area (or area's in case of one at a time printing) is # then displayed as a transparent shadow. If the adhesion type is set to raft, the area is extruded # to represent the raft as well. - def __init__(self, node, hull, thickness, parent = None): + def __init__(self, node: SceneNode, hull: Optional[Polygon], thickness: float, parent: Optional[SceneNode] = None) -> None: super().__init__(parent) self.setCalculateBoundingBox(False) @@ -25,7 +28,11 @@ class ConvexHullNode(SceneNode): # Color of the drawn convex hull if not Application.getInstance().getIsHeadLess(): - self._color = Color(*Application.getInstance().getTheme().getColor("convex_hull").getRgb()) + theme = QtApplication.getInstance().getTheme() + if theme: + self._color = Color(*theme.getColor("convex_hull").getRgb()) + else: + self._color = Color(0, 0, 0) else: self._color = Color(0, 0, 0) @@ -75,7 +82,7 @@ class ConvexHullNode(SceneNode): return True - def _onNodeDecoratorsChanged(self, node): + def _onNodeDecoratorsChanged(self, node: SceneNode) -> None: convex_hull_head = self._node.callDecoration("getConvexHullHead") if convex_hull_head: convex_hull_head_builder = MeshBuilder() diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index f7ad108ad4..33c386bae2 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -64,7 +64,7 @@ class MachineManager(QObject): self.machine_extruder_material_update_dict = collections.defaultdict(list) #type: Dict[str, List[Callable[[], None]]] - self._instance_container_timer = QTimer() #type: QTimer + self._instance_container_timer = QTimer() # type: QTimer self._instance_container_timer.setInterval(250) self._instance_container_timer.setSingleShot(True) self._instance_container_timer.timeout.connect(self.__emitChangedSignals) @@ -74,7 +74,7 @@ class MachineManager(QObject): self._application.globalContainerStackChanged.connect(self._onGlobalContainerChanged) self._container_registry.containerLoadComplete.connect(self._onContainersChanged) - ## When the global container is changed, active material probably needs to be updated. + # When the global container is changed, active material probably needs to be updated. self.globalContainerChanged.connect(self.activeMaterialChanged) self.globalContainerChanged.connect(self.activeVariantChanged) self.globalContainerChanged.connect(self.activeQualityChanged) @@ -115,15 +115,15 @@ class MachineManager(QObject): 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 + title = catalog.i18nc("@info:title", "Incompatible Material")) # type: Message - containers = CuraContainerRegistry.getInstance().findInstanceContainers(id = self.activeMaterialId) #type: List[InstanceContainer] + containers = CuraContainerRegistry.getInstance().findInstanceContainers(id = self.activeMaterialId) # type: List[InstanceContainer] if containers: containers[0].nameChanged.connect(self._onMaterialNameChanged) - self._material_manager = self._application.getMaterialManager() #type: MaterialManager - self._variant_manager = self._application.getVariantManager() #type: VariantManager - self._quality_manager = self._application.getQualityManager() #type: QualityManager + self._material_manager = self._application.getMaterialManager() # type: MaterialManager + self._variant_manager = self._application.getVariantManager() # type: VariantManager + self._quality_manager = self._application.getQualityManager() # type: QualityManager # When the materials lookup table gets updated, it can mean that a material has its name changed, which should # be reflected on the GUI. This signal emission makes sure that it happens. @@ -156,7 +156,7 @@ class MachineManager(QObject): blurSettings = pyqtSignal() # Emitted to force fields in the advanced sidebar to un-focus, so they update properly outputDevicesChanged = pyqtSignal() - currentConfigurationChanged = pyqtSignal() # Emitted every time the current configurations of the machine changes + currentConfigurationChanged = pyqtSignal() # Emitted every time the current configurations of the machine changes printerConnectedStatusChanged = pyqtSignal() # Emitted every time the active machine change or the outputdevices change rootMaterialChanged = pyqtSignal() @@ -201,7 +201,7 @@ class MachineManager(QObject): extruder_configuration.hotendID = extruder.variant.getName() if extruder.variant != empty_variant_container else None self._current_printer_configuration.extruderConfigurations.append(extruder_configuration) - # an empty build plate configuration from the network printer is presented as an empty string, so use "" for an + # An empty build plate configuration from the network printer is presented as an empty string, so use "" for an # empty build plate. self._current_printer_configuration.buildplateConfiguration = self._global_container_stack.getProperty("machine_buildplate_type", "value") if self._global_container_stack.variant != empty_variant_container else "" self.currentConfigurationChanged.emit() @@ -247,7 +247,7 @@ class MachineManager(QObject): self.updateNumberExtrudersEnabled() self.globalContainerChanged.emit() - # after switching the global stack we reconnect all the signals and set the variant and material references + # After switching the global stack we reconnect all the signals and set the variant and material references if self._global_container_stack: self._application.getPreferences().setValue("cura/active_machine", self._global_container_stack.getId()) @@ -261,7 +261,7 @@ class MachineManager(QObject): if global_variant.getMetaDataEntry("hardware_type") != "buildplate": self._global_container_stack.setVariant(empty_variant_container) - # set the global material to empty as we now use the extruder stack at all times - CURA-4482 + # Set the global material to empty as we now use the extruder stack at all times - CURA-4482 global_material = self._global_container_stack.material if global_material != empty_material_container: self._global_container_stack.setMaterial(empty_material_container) @@ -419,7 +419,7 @@ class MachineManager(QObject): # Not a very pretty solution, but the extruder manager doesn't really know how many extruders there are machine_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value") extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks() - count = 1 # we start with the global stack + count = 1 # We start with the global stack for stack in extruder_stacks: md = stack.getMetaData() if "position" in md and int(md["position"]) >= machine_extruder_count: @@ -616,6 +616,14 @@ class MachineManager(QObject): is_supported = self._current_quality_group.is_available return is_supported + @pyqtProperty(bool, notify = activeQualityGroupChanged) + def isActiveQualityExperimental(self) -> bool: + is_experimental = False + if self._global_container_stack: + if self._current_quality_group: + is_experimental = self._current_quality_group.is_experimental + return is_experimental + ## Returns whether there is anything unsupported in the current set-up. # # The current set-up signifies the global stack and all extruder stacks, @@ -646,7 +654,7 @@ class MachineManager(QObject): new_value = self._active_container_stack.getProperty(key, "value") extruder_stacks = [stack for stack in ExtruderManager.getInstance().getActiveExtruderStacks()] - # check in which stack the value has to be replaced + # Check in which stack the value has to be replaced for extruder_stack in extruder_stacks: if extruder_stack != self._active_container_stack and extruder_stack.getProperty(key, "value") != new_value: extruder_stack.userChanges.setProperty(key, "value", new_value) # TODO: nested property access, should be improved @@ -662,7 +670,7 @@ class MachineManager(QObject): for key in self._active_container_stack.userChanges.getAllKeys(): new_value = self._active_container_stack.getProperty(key, "value") - # check if the value has to be replaced + # Check if the value has to be replaced extruder_stack.userChanges.setProperty(key, "value", new_value) @pyqtProperty(str, notify = activeVariantChanged) @@ -731,7 +739,7 @@ class MachineManager(QObject): # If the machine that is being removed is the currently active machine, set another machine as the active machine. activate_new_machine = (self._global_container_stack and self._global_container_stack.getId() == machine_id) - # activate a new machine before removing a machine because this is safer + # Activate a new machine before removing a machine because this is safer if activate_new_machine: machine_stacks = CuraContainerRegistry.getInstance().findContainerStacksMetadata(type = "machine") other_machine_stacks = [s for s in machine_stacks if s["id"] != machine_id] @@ -915,9 +923,12 @@ class MachineManager(QObject): if settable_per_extruder: limit_to_extruder = int(self._global_container_stack.getProperty(setting_key, "limit_to_extruder")) - extruder_position = str(max(0, limit_to_extruder)) - extruder_stack = self._global_container_stack.extruders[extruder_position] - extruder_stack.userChanges.setProperty(setting_key, "value", global_user_container.getProperty(setting_key, "value")) + extruder_position = max(0, limit_to_extruder) + extruder_stack = self.getExtruder(extruder_position) + if extruder_stack: + extruder_stack.userChanges.setProperty(setting_key, "value", global_user_container.getProperty(setting_key, "value")) + else: + Logger.log("e", "Unable to find extruder on position %s", extruder_position) global_user_container.removeInstance(setting_key) # Signal that the global stack has changed @@ -926,10 +937,9 @@ class MachineManager(QObject): @pyqtSlot(int, result = QObject) def getExtruder(self, position: int) -> Optional[ExtruderStack]: - extruder = None if self._global_container_stack: - extruder = self._global_container_stack.extruders.get(str(position)) - return extruder + return self._global_container_stack.extruders.get(str(position)) + return None def updateDefaultExtruder(self) -> None: if self._global_container_stack is None: @@ -995,12 +1005,12 @@ class MachineManager(QObject): if not enabled and position == ExtruderManager.getInstance().activeExtruderIndex: ExtruderManager.getInstance().setActiveExtruderIndex(int(self._default_extruder_position)) - # ensure that the quality profile is compatible with current combination, or choose a compatible one if available + # Ensure that the quality profile is compatible with current combination, or choose a compatible one if available self._updateQualityWithMaterial() self.extruderChanged.emit() - # update material compatibility color + # Update material compatibility color self.activeQualityGroupChanged.emit() - # update items in SettingExtruder + # Update items in SettingExtruder ExtruderManager.getInstance().extrudersChanged.emit(self._global_container_stack.getId()) # Make sure the front end reflects changes self.forceUpdateAllSettings() @@ -1074,7 +1084,6 @@ class MachineManager(QObject): return result - # # Sets all quality and quality_changes containers to empty_quality and empty_quality_changes containers # for all stacks in the currently active machine. # @@ -1133,7 +1142,7 @@ class MachineManager(QObject): def _setQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup") -> None: if self._global_container_stack is None: - return #Can't change that. + return # Can't change that. quality_type = quality_changes_group.quality_type # A custom quality can be created based on "not supported". # In that case, do not set quality containers to empty. @@ -1203,7 +1212,7 @@ class MachineManager(QObject): self.rootMaterialChanged.emit() def activeMaterialsCompatible(self) -> bool: - # check material - variant compatibility + # Check material - variant compatibility if self._global_container_stack is not None: if Util.parseBool(self._global_container_stack.getMetaDataEntry("has_materials", False)): for position, extruder in self._global_container_stack.extruders.items(): @@ -1413,7 +1422,7 @@ class MachineManager(QObject): material_diameter, root_material_id) self.setMaterial(position, material_node) - ## global_stack: if you want to provide your own global_stack instead of the current active one + ## Global_stack: if you want to provide your own global_stack instead of the current active one # if you update an active machine, special measures have to be taken. @pyqtSlot(str, "QVariant") def setMaterial(self, position: str, container_node, global_stack: Optional["GlobalStack"] = None) -> None: diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 273dc0b6f6..9679360ad5 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -72,7 +72,7 @@ class GcodeStartEndFormatter(Formatter): # "-1" is global stack, and if the setting value exists in the global stack, use it as the fallback value. if key in kwargs["-1"]: value = kwargs["-1"] - if key in kwargs[str(extruder_nr)]: + if str(extruder_nr) in kwargs and key in kwargs[str(extruder_nr)]: value = kwargs[str(extruder_nr)][key] if value == default_value_str: diff --git a/plugins/PostProcessingPlugin/PostProcessingPlugin.py b/plugins/PostProcessingPlugin/PostProcessingPlugin.py index 11ee610bec..78f9cc0516 100644 --- a/plugins/PostProcessingPlugin/PostProcessingPlugin.py +++ b/plugins/PostProcessingPlugin/PostProcessingPlugin.py @@ -55,14 +55,14 @@ class PostProcessingPlugin(QObject, Extension): def selectedScriptDefinitionId(self) -> Optional[str]: try: return self._script_list[self._selected_script_index].getDefinitionId() - except: + except IndexError: return "" @pyqtProperty(str, notify=selectedIndexChanged) def selectedScriptStackId(self) -> Optional[str]: try: return self._script_list[self._selected_script_index].getStackId() - except: + except IndexError: return "" ## Execute all post-processing scripts on the gcode. diff --git a/plugins/PostProcessingPlugin/scripts/ExampleScript.md b/plugins/PostProcessingPlugin/scripts/ExampleScript.md new file mode 100644 index 0000000000..08652132aa --- /dev/null +++ b/plugins/PostProcessingPlugin/scripts/ExampleScript.md @@ -0,0 +1,3 @@ +A good example script is SearchAndReplace.py. +If you have any questions please ask them at: +https://github.com/Ultimaker/Cura/issues \ No newline at end of file diff --git a/plugins/PostProcessingPlugin/scripts/ExampleScript.py b/plugins/PostProcessingPlugin/scripts/ExampleScript.py deleted file mode 100644 index 416a5f5404..0000000000 --- a/plugins/PostProcessingPlugin/scripts/ExampleScript.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright (c) 2015 Jaime van Kessel, Ultimaker B.V. -# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher. -from ..Script import Script - -class ExampleScript(Script): - def __init__(self): - super().__init__() - - def getSettingDataString(self): - return """{ - "name":"Example script", - "key": "ExampleScript", - "metadata": {}, - "version": 2, - "settings": - { - "test": - { - "label": "Test", - "description": "None", - "unit": "mm", - "type": "float", - "default_value": 0.5, - "minimum_value": "0", - "minimum_value_warning": "0.1", - "maximum_value_warning": "1" - }, - "derp": - { - "label": "zomg", - "description": "afgasgfgasfgasf", - "unit": "mm", - "type": "float", - "default_value": 0.5, - "minimum_value": "0", - "minimum_value_warning": "0.1", - "maximum_value_warning": "1" - } - } - }""" - - def execute(self, data): - return data \ No newline at end of file diff --git a/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml b/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml index a48cb2ee3f..9121b97eca 100644 --- a/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml +++ b/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml @@ -36,12 +36,19 @@ Item var pg_name = "printingGuidelines" return (pg_name in packageData.links) ? packageData.links[pg_name] : undefined } + + property var materialWebsiteUrl: + { + var pg_name = "website" + return (pg_name in packageData.links) ? packageData.links[pg_name] : undefined + } anchors.topMargin: UM.Theme.getSize("default_margin").height height: visible ? childrenRect.height : 0 visible: packageData.type == "material" && (packageData.has_configs || technicalDataSheetUrl !== undefined || - safetyDataSheetUrl !== undefined || printingGuidelinesUrl !== undefined) + safetyDataSheetUrl !== undefined || printingGuidelinesUrl !== undefined || + materialWebsiteUrl !== undefined) Item { @@ -180,7 +187,8 @@ Item anchors.top: combatibilityItem.bottom anchors.topMargin: UM.Theme.getSize("default_margin").height / 2 visible: base.technicalDataSheetUrl !== undefined || - base.safetyDataSheetUrl !== undefined || base.printingGuidelinesUrl !== undefined + base.safetyDataSheetUrl !== undefined || base.printingGuidelinesUrl !== undefined || + base.materialWebsiteUrl !== undefined height: visible ? contentHeight : 0 text: { @@ -208,6 +216,16 @@ Item var pg_name = catalog.i18nc("@action:label", "Printing Guidelines") result += "%2".arg(base.printingGuidelinesUrl).arg(pg_name) } + if (base.materialWebsiteUrl !== undefined) + { + if (result.length > 0) + { + result += "
" + } + var pg_name = catalog.i18nc("@action:label", "Website") + result += "%2".arg(base.materialWebsiteUrl).arg(pg_name) + } + return result } font: UM.Theme.getFont("very_small") diff --git a/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml b/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml index 437a2ef351..7b48aaebef 100644 --- a/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml +++ b/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml @@ -144,10 +144,6 @@ Item { return "" } - if (details.author_email) - { - return "" + details.author_name + "" - } else { return "" + details.author_name + "" diff --git a/plugins/Toolbox/resources/qml/ToolboxDetailTile.qml b/plugins/Toolbox/resources/qml/ToolboxDetailTile.qml index 9061a8e06b..1d701543ce 100644 --- a/plugins/Toolbox/resources/qml/ToolboxDetailTile.qml +++ b/plugins/Toolbox/resources/qml/ToolboxDetailTile.qml @@ -37,7 +37,7 @@ Item anchors.top: packageName.bottom width: parent.width text: model.description - maximumLineCount: 6 + maximumLineCount: 25 elide: Text.ElideRight wrapMode: Text.WordWrap color: UM.Theme.getColor("text") diff --git a/plugins/UM3NetworkPrinting/resources/png/Ultimaker 3 Extended.png b/plugins/UM3NetworkPrinting/resources/png/Ultimaker 3 Extended.png new file mode 100644 index 0000000000..1ce19c2933 Binary files /dev/null and b/plugins/UM3NetworkPrinting/resources/png/Ultimaker 3 Extended.png differ diff --git a/plugins/UM3NetworkPrinting/resources/png/Ultimaker 3.png b/plugins/UM3NetworkPrinting/resources/png/Ultimaker 3.png new file mode 100644 index 0000000000..4639cb3fde Binary files /dev/null and b/plugins/UM3NetworkPrinting/resources/png/Ultimaker 3.png differ diff --git a/plugins/UM3NetworkPrinting/resources/png/Ultimaker S5.png b/plugins/UM3NetworkPrinting/resources/png/Ultimaker S5.png new file mode 100644 index 0000000000..29ba428e38 Binary files /dev/null and b/plugins/UM3NetworkPrinting/resources/png/Ultimaker S5.png differ diff --git a/plugins/UM3NetworkPrinting/resources/qml/CameraButton.qml b/plugins/UM3NetworkPrinting/resources/qml/CameraButton.qml index 7e5c254e5c..618dbed81c 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/CameraButton.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/CameraButton.qml @@ -9,10 +9,10 @@ import Cura 1.0 as Cura Rectangle { property var iconSource: null; - color: clickArea.containsMouse ? UM.Theme.getColor("primary_hover") : UM.Theme.getColor("primary"); // "Cura Blue" + color: "#0a0850" // TODO: Theme! height: width; radius: Math.round(0.5 * width); - width: 36 * screenScaleFactor; + width: 24 * screenScaleFactor; UM.RecolorImage { id: icon; diff --git a/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml b/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml index 19a152e6eb..adf5ea5e1c 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml @@ -10,14 +10,13 @@ import QtGraphicalEffects 1.0 Component { - Rectangle + Item { id: monitorFrame property var emphasisColor: UM.Theme.getColor("setting_control_border_highlight") property var cornerRadius: UM.Theme.getSize("monitor_corner_radius").width - color: "transparent" height: maximumHeight onVisibleChanged: { @@ -48,13 +47,45 @@ Component } } + ScrollView + { + id: printers + anchors + { + left: queue.left + right: queue.right + top: parent.top + topMargin: 48 * screenScaleFactor // TODO: Theme! + } + height: 264 * screenScaleFactor // TODO: Theme! + + Row + { + spacing: 60 * screenScaleFactor // TODO: Theme! + + Repeater + { + model: OutputDevice.printers + + MonitorPrinterCard + { + printer: modelData + } + } + } + } + Item { id: queue + width: Math.min(834 * screenScaleFactor, maximumWidth) - anchors.fill: parent - anchors.top: parent.top - anchors.topMargin: 400 * screenScaleFactor // TODO: Insert carousel here + anchors { + bottom: parent.bottom + horizontalCenter: parent.horizontalCenter + top: printers.bottom + topMargin: 48 * screenScaleFactor // TODO: Theme! + } Label { @@ -104,7 +135,6 @@ Component text: catalog.i18nc("@label link to connect manager", "Manage queue in Cura Connect") } } - MouseArea { @@ -187,7 +217,7 @@ Component } style: UM.Theme.styles.scrollview visible: OutputDevice.receivedPrintJobs - width: Math.min(834 * screenScaleFactor, maximumWidth) + width: parent.width ListView { @@ -214,5 +244,4 @@ Component visible: OutputDevice.activeCameraUrl != "" } } - } diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobPreview.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobPreview.qml index 1a69d2dc12..ec26bbe568 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobPreview.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobPreview.qml @@ -21,7 +21,14 @@ Item { id: previewImage anchors.fill: parent - opacity: printJob && printJob.state == "error" ? 0.5 : 1.0 + opacity: + { + if (printJob && (printJob.state == "error" || !printJob.isActive)) + { + return 0.5 + } + return 1.0 + } source: printJob ? printJob.previewImageUrl : "" visible: printJob } @@ -47,11 +54,28 @@ Item UM.RecolorImage { - id: statusImage + id: overlayIcon anchors.centerIn: printJobPreview color: UM.Theme.getColor("monitor_image_overlay") height: 0.5 * printJobPreview.height - source: printJob && printJob.state == "error" ? "../svg/aborted-icon.svg" : "" + source: + { + switch(printJob.state) + { + case "error": + return "../svg/aborted-icon.svg" + case "wait_cleanup": + return printJob.timeTotal > printJob.timeElapsed ? "../svg/aborted-icon.svg" : "" + case "pausing": + return "../svg/paused-icon.svg" + case "paused": + return "../svg/paused-icon.svg" + case "resuming": + return "../svg/paused-icon.svg" + default: + return "" + } + } sourceSize { height: height diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobProgressBar.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobProgressBar.qml new file mode 100644 index 0000000000..88418516ed --- /dev/null +++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrintJobProgressBar.qml @@ -0,0 +1,117 @@ +// Copyright (c) 2018 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.3 +import QtQuick.Controls.Styles 1.3 +import QtQuick.Controls 1.4 +import UM 1.3 as UM + +/** + * 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 +{ + id: base + + // The print job which all other information is dervied from + property var printJob: null + + width: childrenRect.width + height: 18 * screenScaleFactor // TODO: Theme! + + ProgressBar + { + id: progressBar + anchors + { + verticalCenter: parent.verticalCenter + } + value: printJob ? printJob.progress : 0 + style: ProgressBarStyle + { + background: Rectangle + { + color: printJob && printJob.isActive ? "#e4e4f2" : "#f3f3f9" // TODO: Theme! + implicitHeight: visible ? 8 * screenScaleFactor : 0 // TODO: Theme! + implicitWidth: 180 * screenScaleFactor // TODO: Theme! + radius: 4 * screenScaleFactor // TODO: Theme! + } + progress: Rectangle + { + id: progressItem; + color: printJob && printJob.isActive ? "#0a0850" : "#9392b2" // TODO: Theme! + radius: 4 * screenScaleFactor // TODO: Theme! + } + } + } + Label + { + id: percentLabel + anchors + { + left: progressBar.right + leftMargin: 18 * screenScaleFactor // TODO: Theme! + } + text: Math.round(printJob.progress * 100) + "%" + color: printJob && printJob.isActive ? "#374355" : "#babac1" // TODO: Theme! + width: contentWidth + font: UM.Theme.getFont("medium") // 14pt, regular + + // FIXED-LINE-HEIGHT: + height: 18 * screenScaleFactor // TODO: Theme! + verticalAlignment: Text.AlignVCenter + } + Label + { + id: statusLabel + anchors + { + left: percentLabel.right + leftMargin: 18 * screenScaleFactor // TODO: Theme! + } + color: "#374355" // TODO: Theme! + font: UM.Theme.getFont("medium") // 14pt, regular + text: + { + if (!printJob) + { + return "" + } + switch (printJob.state) + { + case "wait_cleanup": + if (printJob.timeTotal > printJob.timeElapsed) + { + return catalog.i18nc("@label:status", "Aborted") + } + return catalog.i18nc("@label:status", "Finished") + case "sent_to_printer": + return catalog.i18nc("@label:status", "Preparing...") + case "pre_print": + return catalog.i18nc("@label:status", "Preparing...") + case "aborting": // NOTE: Doesn't exist but maybe should someday + return catalog.i18nc("@label:status", "Aborting...") + case "aborted": // NOTE: Unused, see above + return catalog.i18nc("@label:status", "Aborted") + case "pausing": + return catalog.i18nc("@label:status", "Pausing...") + case "paused": + return catalog.i18nc("@label:status", "Paused") + case "resuming": + return catalog.i18nc("@label:status", "Resuming...") + case "queued": + return catalog.i18nc("@label:status", "Action required") + default: + return catalog.i18nc("@label:status", "Finishes %1 at %2".arg(OutputDevice.getDateCompleted( printJob.timeRemaining )).arg(OutputDevice.getTimeCompleted( printJob.timeRemaining ))) + } + } + width: contentWidth + + // FIXED-LINE-HEIGHT: + height: 18 * screenScaleFactor // TODO: Theme! + verticalAlignment: Text.AlignVCenter + } +} \ No newline at end of file diff --git a/plugins/UM3NetworkPrinting/resources/qml/MonitorPrinterCard.qml b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrinterCard.qml new file mode 100644 index 0000000000..8659037cb8 --- /dev/null +++ b/plugins/UM3NetworkPrinting/resources/qml/MonitorPrinterCard.qml @@ -0,0 +1,269 @@ +// 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 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 + * with '// FIXED-LINE-HEIGHT:'. + */ +Item +{ + id: base + + // The printer which all printer data is derived from + property var printer: null + + property var borderSize: 1 * screenScaleFactor // TODO: Theme, and remove from here + + width: 834 * screenScaleFactor // TODO: Theme! + height: 216 * screenScaleFactor // TODO: Theme! + + // Printer portion + Rectangle + { + id: printerInfo + border + { + color: "#EAEAEC" // TODO: Theme! + width: borderSize // TODO: Remove once themed + } + color: "white" // TODO: Theme! + width: parent.width + height: 144 * screenScaleFactor // TODO: Theme! + + Row + { + anchors + { + left: parent.left + leftMargin: 36 * screenScaleFactor // TODO: Theme! + verticalCenter: parent.verticalCenter + } + spacing: 18 * screenScaleFactor // TODO: Theme! + + Image + { + id: printerImage + width: 108 * screenScaleFactor // TODO: Theme! + height: 108 * screenScaleFactor // TODO: Theme! + fillMode: Image.PreserveAspectFit + source: "../png/" + printer.type + ".png" + mipmap: true + } + + Item + { + anchors + { + verticalCenter: parent.verticalCenter + } + width: 216 * screenScaleFactor // TODO: Theme! + height: printerNameLabel.height + printerFamilyPill.height + 6 * screenScaleFactor // TODO: Theme! + + Label + { + id: printerNameLabel + text: printer && printer.name ? printer.name : "" + color: "#414054" // TODO: Theme! + elide: Text.ElideRight + font: UM.Theme.getFont("large") // 16pt, bold + width: parent.width + + // FIXED-LINE-HEIGHT: + height: 18 * screenScaleFactor // TODO: Theme! + verticalAlignment: Text.AlignVCenter + } + + MonitorPrinterPill + { + id: printerFamilyPill + anchors + { + top: printerNameLabel.bottom + topMargin: 6 * screenScaleFactor // TODO: Theme! + left: printerNameLabel.left + } + text: printer.type + } + } + + MonitorPrinterConfiguration + { + id: printerConfiguration + anchors.verticalCenter: parent.verticalCenter + buildplate: "Glass" + configurations: + [ + base.printer.printerConfiguration.extruderConfigurations[0], + base.printer.printerConfiguration.extruderConfigurations[1] + ] + height: 72 * screenScaleFactor // TODO: Theme! + } + } + + PrintJobContextMenu + { + id: contextButton + anchors + { + right: parent.right + rightMargin: 12 * screenScaleFactor // TODO: Theme! + top: parent.top + topMargin: 12 * screenScaleFactor // TODO: Theme! + } + printJob: printer.activePrintJob + width: 36 * screenScaleFactor // TODO: Theme! + height: 36 * screenScaleFactor // TODO: Theme! + } + CameraButton + { + id: cameraButton; + anchors + { + right: parent.right + rightMargin: 20 * screenScaleFactor // TODO: Theme! + bottom: parent.bottom + bottomMargin: 20 * screenScaleFactor // TODO: Theme! + } + iconSource: "../svg/icons/camera.svg" + } + } + + + // Print job portion + Rectangle + { + id: printJobInfo + anchors + { + top: printerInfo.bottom + topMargin: -borderSize * screenScaleFactor // TODO: Theme! + } + border + { + color: "#EAEAEC" // TODO: Theme! + width: borderSize // TODO: Remove once themed + } + color: "white" // TODO: Theme! + height: 84 * screenScaleFactor + borderSize // TODO: Remove once themed + width: parent.width + + Row + { + anchors + { + fill: parent + topMargin: 12 * screenScaleFactor + borderSize // TODO: Theme! + bottomMargin: 12 * screenScaleFactor // TODO: Theme! + leftMargin: 36 * screenScaleFactor // TODO: Theme! + } + height: childrenRect.height + spacing: 18 * screenScaleFactor // TODO: Theme! + + Label + { + id: printerStatus + anchors + { + verticalCenter: parent.verticalCenter + } + color: "#414054" // TODO: Theme! + font: UM.Theme.getFont("large") // 16pt, bold + text: { + if (printer && printer.state == "disabled"){ + return catalog.i18nc("@label:status", "Unavailable") + } + if (printer && printer.state == "unreachable"){ + return catalog.i18nc("@label:status", "Unavailable") + } + if (printer && !printer.activePrintJob) + { + return catalog.i18nc("@label:status", "Idle") + } + return "" + } + } + + Item + { + anchors + { + verticalCenter: parent.verticalCenter + } + width: printerImage.width + height: 60 * screenScaleFactor // TODO: Theme! + MonitorPrintJobPreview + { + anchors.centerIn: parent + printJob: base.printer.activePrintJob + size: parent.height + } + visible: printer.activePrintJob + } + + Item + { + anchors + { + verticalCenter: parent.verticalCenter + } + width: 216 * screenScaleFactor // TODO: Theme! + height: printerNameLabel.height + printerFamilyPill.height + 6 * screenScaleFactor // TODO: Theme! + visible: printer.activePrintJob + + Label + { + id: printerJobNameLabel + color: printer.activePrintJob && printer.activePrintJob.isActive ? "#414054" : "#babac1" // TODO: Theme! + elide: Text.ElideRight + font: UM.Theme.getFont("large") // 16pt, bold + text: base.printer.activePrintJob ? base.printer.activePrintJob.name : "Untitled" // TODO: I18N + width: parent.width + + // FIXED-LINE-HEIGHT: + height: 18 * screenScaleFactor // TODO: Theme! + verticalAlignment: Text.AlignVCenter + } + + Label + { + id: printerJobOwnerLabel + anchors + { + top: printerJobNameLabel.bottom + topMargin: 6 * screenScaleFactor // TODO: Theme! + left: printerJobNameLabel.left + } + color: printer.activePrintJob && printer.activePrintJob.isActive ? "#53657d" : "#babac1" // TODO: Theme! + elide: Text.ElideRight + font: UM.Theme.getFont("very_small") // 12pt, regular + text: printer.activePrintJob ? printer.activePrintJob.owner : "Anonymous" // TODO: I18N + width: parent.width + + // FIXED-LINE-HEIGHT: + height: 18 * screenScaleFactor // TODO: Theme! + verticalAlignment: Text.AlignVCenter + } + } + + MonitorPrintJobProgressBar + { + anchors + { + verticalCenter: parent.verticalCenter + } + printJob: printer.activePrintJob + visible: printer.activePrintJob + } + } + } +} \ No newline at end of file diff --git a/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml b/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml index 11bc913d06..02a8e7ae69 100644 --- a/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml +++ b/plugins/UM3NetworkPrinting/resources/qml/PrintJobContextMenu.qml @@ -25,7 +25,7 @@ Item { } contentItem: Label { color: UM.Theme.getColor("monitor_context_menu_dots"); - font.pixelSize: 25 * screenScaleFactor; + font.pixelSize: 32 * screenScaleFactor; horizontalAlignment: Text.AlignHCenter; text: button.text; verticalAlignment: Text.AlignVCenter; @@ -41,7 +41,7 @@ Item { var states = ["queued", "sent_to_printer", "pre_print", "printing", "pausing", "paused", "resuming"]; return states.indexOf(printJob.state) !== -1; } - width: 35 * screenScaleFactor; // TODO: Theme! + width: 36 * screenScaleFactor; // TODO: Theme! } Popup { diff --git a/plugins/UM3NetworkPrinting/resources/svg/icons/camera.svg b/plugins/UM3NetworkPrinting/resources/svg/icons/camera.svg new file mode 100644 index 0000000000..2eaebb812d --- /dev/null +++ b/plugins/UM3NetworkPrinting/resources/svg/icons/camera.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py b/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py index f8726a5441..1e0f538d8d 100644 --- a/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/ClusterUM3OutputDevice.py @@ -65,7 +65,6 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): self._received_print_jobs = False # type: bool self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../resources/qml/ClusterMonitorItem.qml") - self._control_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../resources/qml/ClusterControlItem.qml") # See comments about this hack with the clusterPrintersChanged signal self.printersChanged.connect(self.clusterPrintersChanged) @@ -387,8 +386,24 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): @pyqtSlot(int, result = str) def getDateCompleted(self, time_remaining: int) -> str: current_time = time() - datetime_completed = datetime.fromtimestamp(current_time + time_remaining) - return (datetime_completed.strftime("%a %b ") + "{day}".format(day=datetime_completed.day)).upper() + completed = datetime.fromtimestamp(current_time + time_remaining) + today = datetime.fromtimestamp(current_time) + + # If finishing date is more than 7 days out, using "Mon Dec 3 at HH:MM" format + if completed.toordinal() > today.toordinal() + 7: + return completed.strftime("%a %b ") + "{day}".format(day=completed.day) + + # If finishing date is within the next week, use "Monday at HH:MM" format + elif completed.toordinal() > today.toordinal() + 1: + return completed.strftime("%a") + + # If finishing tomorrow, use "tomorrow at HH:MM" format + elif completed.toordinal() > today.toordinal(): + return "tomorrow" + + # If finishing today, use "today at HH:MM" format + else: + return "today" @pyqtSlot(str) def sendJobToTop(self, print_job_uuid: str) -> None: diff --git a/plugins/UM3NetworkPrinting/src/Models.py b/plugins/UM3NetworkPrinting/src/Models.py new file mode 100644 index 0000000000..2bcac70766 --- /dev/null +++ b/plugins/UM3NetworkPrinting/src/Models.py @@ -0,0 +1,43 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + + +## Base model that maps kwargs to instance attributes. +class BaseModel: + def __init__(self, **kwargs) -> None: + self.__dict__.update(kwargs) + self.validate() + + def validate(self) -> None: + pass + + +## Class representing a material that was fetched from the cluster API. +class ClusterMaterial(BaseModel): + def __init__(self, guid: str, version: int, **kwargs) -> None: + self.guid = guid # type: str + self.version = version # type: int + super().__init__(**kwargs) + + def validate(self) -> None: + if not self.guid: + raise ValueError("guid is required on ClusterMaterial") + if not self.version: + raise ValueError("version is required on ClusterMaterial") + + +## Class representing a local material that was fetched from the container registry. +class LocalMaterial(BaseModel): + def __init__(self, GUID: str, id: str, version: int, **kwargs) -> None: + self.GUID = GUID # type: str + self.id = id # type: str + self.version = version # type: int + super().__init__(**kwargs) + + def validate(self) -> None: + if not self.GUID: + raise ValueError("guid is required on LocalMaterial") + if not self.version: + raise ValueError("version is required on LocalMaterial") + if not self.id: + raise ValueError("id is required on LocalMaterial") diff --git a/plugins/UM3NetworkPrinting/src/SendMaterialJob.py b/plugins/UM3NetworkPrinting/src/SendMaterialJob.py index 8491e79c29..f536fad49a 100644 --- a/plugins/UM3NetworkPrinting/src/SendMaterialJob.py +++ b/plugins/UM3NetworkPrinting/src/SendMaterialJob.py @@ -1,99 +1,197 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +import json +import os +import urllib.parse +from typing import Dict, TYPE_CHECKING, Set -import json #To understand the list of materials from the printer reply. -import os #To walk over material files. -import os.path #To filter on material files. -from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest #To listen to the reply from the printer. -from typing import Any, Dict, Set, TYPE_CHECKING -import urllib.parse #For getting material IDs from their file names. +from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest -from UM.Job import Job #The interface we're implementing. +from UM.Application import Application +from UM.Job import Job from UM.Logger import Logger -from UM.MimeTypeDatabase import MimeTypeDatabase #To strip the extensions of the material profile files. +from UM.MimeTypeDatabase import MimeTypeDatabase from UM.Resources import Resources -from UM.Settings.ContainerRegistry import ContainerRegistry #To find the GUIDs of materials. - -from cura.CuraApplication import CuraApplication #For the resource types. +from cura.CuraApplication import CuraApplication +# Absolute imports don't work in plugins +from .Models import ClusterMaterial, LocalMaterial if TYPE_CHECKING: from .ClusterUM3OutputDevice import ClusterUM3OutputDevice + ## Asynchronous job to send material profiles to the printer. # # This way it won't freeze up the interface while sending those materials. class SendMaterialJob(Job): + def __init__(self, device: "ClusterUM3OutputDevice") -> None: super().__init__() - self.device = device #type: ClusterUM3OutputDevice + self.device = device # type: ClusterUM3OutputDevice + ## Send the request to the printer and register a callback def run(self) -> None: - self.device.get("materials/", on_finished = self.sendMissingMaterials) + self.device.get("materials/", on_finished = self._onGetRemoteMaterials) - def sendMissingMaterials(self, reply: QNetworkReply) -> None: - if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200: #Got an error from the HTTP request. - Logger.log("e", "Couldn't request current material storage on printer. Not syncing materials.") + ## Process the materials reply from the printer. + # + # \param reply The reply from the printer, a json file. + def _onGetRemoteMaterials(self, reply: QNetworkReply) -> None: + + # Got an error from the HTTP request. If we did not receive a 200 something happened. + if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200: + Logger.log("e", "Error fetching materials from printer: %s", reply.errorString()) return - remote_materials_list = reply.readAll().data().decode("utf-8") - try: - remote_materials_list = json.loads(remote_materials_list) - except json.JSONDecodeError: - Logger.log("e", "Request material storage on printer: I didn't understand the printer's answer.") - return - try: - remote_materials_by_guid = {material["guid"]: material for material in remote_materials_list} #Index by GUID. - except KeyError: - Logger.log("e", "Request material storage on printer: Printer's answer was missing GUIDs.") + # Collect materials from the printer's reply and send the missing ones if needed. + remote_materials_by_guid = self._parseReply(reply) + if remote_materials_by_guid: + self._sendMissingMaterials(remote_materials_by_guid) + + ## Determine which materials should be updated and send them to the printer. + # + # \param remote_materials_by_guid The remote materials by GUID. + def _sendMissingMaterials(self, remote_materials_by_guid: Dict[str, ClusterMaterial]) -> None: + + # Collect local materials + local_materials_by_guid = self._getLocalMaterials() + if len(local_materials_by_guid) == 0: + Logger.log("d", "There are no local materials to synchronize with the printer.") return - container_registry = ContainerRegistry.getInstance() - local_materials_list = filter(lambda material: ("GUID" in material and "version" in material and "id" in material), container_registry.findContainersMetadata(type = "material")) - local_materials_by_guid = {material["GUID"]: material for material in local_materials_list if material["id"] == material["base_file"]} - for material in local_materials_list: #For each GUID get the material with the highest version number. - try: - if int(material["version"]) > local_materials_by_guid[material["GUID"]]["version"]: - local_materials_by_guid[material["GUID"]] = material - except ValueError: - Logger.log("e", "Material {material_id} has invalid version number {number}.".format(material_id = material["id"], number = material["version"])) - continue + # Find out what materials are new or updated and must be sent to the printer + material_ids_to_send = self._determineMaterialsToSend(local_materials_by_guid, remote_materials_by_guid) + if len(material_ids_to_send) == 0: + Logger.log("d", "There are no remote materials to update.") + return - materials_to_send = set() #type: Set[Dict[str, Any]] - for guid, material in local_materials_by_guid.items(): - if guid not in remote_materials_by_guid: - materials_to_send.add(material["id"]) - continue - try: - if int(material["version"]) > remote_materials_by_guid[guid]["version"]: - materials_to_send.add(material["id"]) - continue - except KeyError: - Logger.log("e", "Current material storage on printer was an invalid reply (missing version).") - return + # Send materials to the printer + self._sendMaterials(material_ids_to_send) - for file_path in Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.MaterialInstanceContainer): + ## From the local and remote materials, determine which ones should be synchronized. + # + # Makes a Set of id's containing only the id's of the materials that are not on the printer yet or the ones that + # are newer in Cura. + # + # \param local_materials The local materials by GUID. + # \param remote_materials The remote materials by GUID. + @staticmethod + def _determineMaterialsToSend(local_materials: Dict[str, LocalMaterial], + remote_materials: Dict[str, ClusterMaterial]) -> Set[str]: + return { + material.id + for guid, material in local_materials.items() + if guid not in remote_materials or material.version > remote_materials[guid].version + } + + ## Send the materials to the printer. + # + # The given materials will be loaded from disk en sent to to printer. + # The given id's will be matched with filenames of the locally stored materials. + # + # \param materials_to_send A set with id's of materials that must be sent. + def _sendMaterials(self, materials_to_send: Set[str]) -> None: + file_paths = Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.MaterialInstanceContainer) + + # Find all local material files and send them if needed. + for file_path in file_paths: try: mime_type = MimeTypeDatabase.getMimeTypeForFile(file_path) except MimeTypeDatabase.MimeTypeNotFoundError: - continue #Not the sort of file we'd like to send then. - _, file_name = os.path.split(file_path) - material_id = urllib.parse.unquote_plus(mime_type.stripExtension(file_name)) - if material_id not in materials_to_send: continue - parts = [] - with open(file_path, "rb") as f: - parts.append(self.device._createFormPart("name=\"file\"; filename=\"{file_name}\"".format(file_name = file_name), f.read())) - signature_file_path = file_path + ".sig" - if os.path.exists(signature_file_path): - _, signature_file_name = os.path.split(signature_file_path) - with open(signature_file_path, "rb") as f: - parts.append(self.device._createFormPart("name=\"signature_file\"; filename=\"{file_name}\"".format(file_name = signature_file_name), f.read())) + file_name = os.path.basename(file_path) + material_id = urllib.parse.unquote_plus(mime_type.stripExtension(file_name)) + if material_id not in materials_to_send: + # If the material does not have to be sent we skip it. + continue - Logger.log("d", "Syncing material {material_id} with cluster.".format(material_id = material_id)) - self.device.postFormWithParts(target = "materials/", parts = parts, on_finished = self.sendingFinished) + self._sendMaterialFile(file_path, file_name, material_id) - def sendingFinished(self, reply: QNetworkReply): + ## Send a single material file to the printer. + # + # Also add the material signature file if that is available. + # + # \param file_path The path of the material file. + # \param file_name The name of the material file. + # \param material_id The ID of the material in the file. + def _sendMaterialFile(self, file_path: str, file_name: str, material_id: str) -> None: + + parts = [] + + # Add the material file. + with open(file_path, "rb") as f: + parts.append(self.device.createFormPart("name=\"file\"; filename=\"{file_name}\"" + .format(file_name = file_name), f.read())) + + # Add the material signature file if needed. + signature_file_path = "{}.sig".format(file_path) + if os.path.exists(signature_file_path): + signature_file_name = os.path.basename(signature_file_path) + with open(signature_file_path, "rb") as f: + parts.append(self.device.createFormPart("name=\"signature_file\"; filename=\"{file_name}\"" + .format(file_name = signature_file_name), f.read())) + + Logger.log("d", "Syncing material {material_id} with cluster.".format(material_id = material_id)) + self.device.postFormWithParts(target = "materials/", parts = parts, on_finished = self.sendingFinished) + + ## Check a reply from an upload to the printer and log an error when the call failed + @staticmethod + def sendingFinished(reply: QNetworkReply) -> None: if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200: - Logger.log("e", "Received error code from printer when syncing material: {code}".format(code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute))) - Logger.log("e", reply.readAll().data().decode("utf-8")) \ No newline at end of file + Logger.log("e", "Received error code from printer when syncing material: {code}, {text}".format( + code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute), + text = reply.errorString() + )) + + ## Parse the reply from the printer + # + # Parses the reply to a "/materials" request to the printer + # + # \return a dictionary of ClusterMaterial objects by GUID + # \throw KeyError Raised when on of the materials does not include a valid guid + @classmethod + def _parseReply(cls, reply: QNetworkReply) -> Dict[str, ClusterMaterial]: + try: + remote_materials = json.loads(reply.readAll().data().decode("utf-8")) + return {material["guid"]: ClusterMaterial(**material) for material in remote_materials} + except UnicodeDecodeError: + Logger.log("e", "Request material storage on printer: I didn't understand the printer's answer.") + except json.JSONDecodeError: + Logger.log("e", "Request material storage on printer: I didn't understand the printer's answer.") + except ValueError: + Logger.log("e", "Request material storage on printer: Printer's answer had an incorrect value.") + except TypeError: + Logger.log("e", "Request material storage on printer: Printer's answer was missing a required value.") + + ## Retrieves a list of local materials + # + # Only the new newest version of the local materials is returned + # + # \return a dictionary of LocalMaterial objects by GUID + def _getLocalMaterials(self) -> Dict[str, LocalMaterial]: + result = {} # type: Dict[str, LocalMaterial] + container_registry = Application.getInstance().getContainerRegistry() + material_containers = container_registry.findContainersMetadata(type = "material") + + # Find the latest version of all material containers in the registry. + for material in material_containers: + try: + # material version must be an int + material["version"] = int(material["version"]) + + # Create a new local material + local_material = LocalMaterial(**material) + + if local_material.GUID not in result or \ + local_material.version > result.get(local_material.GUID).version: + result[local_material.GUID] = local_material + + except KeyError: + Logger.logException("w", "Local material {} has missing values.".format(material["id"])) + except ValueError: + Logger.logException("w", "Local material {} has invalid values.".format(material["id"])) + except TypeError: + Logger.logException("w", "Local material {} has invalid values.".format(material["id"])) + + return result diff --git a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py index daea696cd1..b96c508d70 100644 --- a/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py +++ b/plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py @@ -341,7 +341,6 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): # Request more data if info is not complete if not info.address: - Logger.log("d", "Trying to get address of %s", name) info = zero_conf.get_service_info(service_type, name) if info: diff --git a/plugins/UM3NetworkPrinting/tests/TestSendMaterialJob.py b/plugins/UM3NetworkPrinting/tests/TestSendMaterialJob.py new file mode 100644 index 0000000000..b669eb192a --- /dev/null +++ b/plugins/UM3NetworkPrinting/tests/TestSendMaterialJob.py @@ -0,0 +1,190 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. +import io +import json +from unittest import TestCase, mock +from unittest.mock import patch, call + +from PyQt5.QtCore import QByteArray + +from UM.MimeTypeDatabase import MimeType +from UM.Application import Application +from plugins.UM3NetworkPrinting.src.SendMaterialJob import SendMaterialJob + + +@patch("builtins.open", lambda _, __: io.StringIO("")) +@patch("UM.MimeTypeDatabase.MimeTypeDatabase.getMimeTypeForFile", + lambda _: MimeType(name = "application/x-ultimaker-material-profile", comment = "Ultimaker Material Profile", + suffixes = ["xml.fdm_material"])) +@patch("UM.Resources.Resources.getAllResourcesOfType", lambda _: ["/materials/generic_pla_white.xml.fdm_material"]) +@patch("plugins.UM3NetworkPrinting.src.ClusterUM3OutputDevice") +@patch("PyQt5.QtNetwork.QNetworkReply") +class TestSendMaterialJob(TestCase): + _LOCAL_MATERIAL_WHITE = {"type": "material", "status": "unknown", "id": "generic_pla_white", + "base_file": "generic_pla_white", "setting_version": "5", "name": "White PLA", + "brand": "Generic", "material": "PLA", "color_name": "White", + "GUID": "badb0ee7-87c8-4f3f-9398-938587b67dce", "version": "1", "color_code": "#ffffff", + "description": "Test PLA White", "adhesion_info": "Use glue.", "approximate_diameter": "3", + "properties": {"density": "1.00", "diameter": "2.85", "weight": "750"}, + "definition": "fdmprinter", "compatible": True} + + _LOCAL_MATERIAL_BLACK = {"type": "material", "status": "unknown", "id": "generic_pla_black", + "base_file": "generic_pla_black", "setting_version": "5", "name": "Yellow CPE", + "brand": "Ultimaker", "material": "CPE", "color_name": "Black", + "GUID": "5fbb362a-41f9-4818-bb43-15ea6df34aa4", "version": "1", "color_code": "#000000", + "description": "Test PLA Black", "adhesion_info": "Use glue.", "approximate_diameter": "3", + "properties": {"density": "1.01", "diameter": "2.85", "weight": "750"}, + "definition": "fdmprinter", "compatible": True} + + _REMOTE_MATERIAL_WHITE = { + "guid": "badb0ee7-87c8-4f3f-9398-938587b67dce", + "material": "PLA", + "brand": "Generic", + "version": 1, + "color": "White", + "density": 1.00 + } + + _REMOTE_MATERIAL_BLACK = { + "guid": "5fbb362a-41f9-4818-bb43-15ea6df34aa4", + "material": "PLA", + "brand": "Generic", + "version": 2, + "color": "Black", + "density": 1.00 + } + + def test_run(self, device_mock, reply_mock): + job = SendMaterialJob(device_mock) + job.run() + + # We expect the materials endpoint to be called when the job runs. + device_mock.get.assert_called_with("materials/", on_finished = job._onGetRemoteMaterials) + + def test__onGetRemoteMaterials_withFailedRequest(self, reply_mock, device_mock): + reply_mock.attribute.return_value = 404 + job = SendMaterialJob(device_mock) + job._onGetRemoteMaterials(reply_mock) + + # We expect the device not to be called for any follow up. + self.assertEqual(0, device_mock.createFormPart.call_count) + + def test__onGetRemoteMaterials_withWrongEncoding(self, reply_mock, device_mock): + reply_mock.attribute.return_value = 200 + reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("cp500")) + job = SendMaterialJob(device_mock) + job._onGetRemoteMaterials(reply_mock) + + # Given that the parsing fails we do no expect the device to be called for any follow up. + self.assertEqual(0, device_mock.createFormPart.call_count) + + def test__onGetRemoteMaterials_withBadJsonAnswer(self, reply_mock, device_mock): + reply_mock.attribute.return_value = 200 + reply_mock.readAll.return_value = QByteArray(b"Six sick hicks nick six slick bricks with picks and sticks.") + job = SendMaterialJob(device_mock) + job._onGetRemoteMaterials(reply_mock) + + # Given that the parsing fails we do no expect the device to be called for any follow up. + self.assertEqual(0, device_mock.createFormPart.call_count) + + def test__onGetRemoteMaterials_withMissingGuidInRemoteMaterial(self, reply_mock, device_mock): + reply_mock.attribute.return_value = 200 + remote_material_without_guid = self._REMOTE_MATERIAL_WHITE.copy() + del remote_material_without_guid["guid"] + reply_mock.readAll.return_value = QByteArray(json.dumps([remote_material_without_guid]).encode("ascii")) + job = SendMaterialJob(device_mock) + job._onGetRemoteMaterials(reply_mock) + + # Given that parsing fails we do not expect the device to be called for any follow up. + self.assertEqual(0, device_mock.createFormPart.call_count) + + @patch("cura.Settings.CuraContainerRegistry") + @patch("UM.Application") + def test__onGetRemoteMaterials_withInvalidVersionInLocalMaterial(self, application_mock, container_registry_mock, + reply_mock, device_mock): + reply_mock.attribute.return_value = 200 + reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("ascii")) + + localMaterialWhiteWithInvalidVersion = self._LOCAL_MATERIAL_WHITE.copy() + localMaterialWhiteWithInvalidVersion["version"] = "one" + container_registry_mock.findContainersMetadata.return_value = [localMaterialWhiteWithInvalidVersion] + + application_mock.getContainerRegistry.return_value = container_registry_mock + + with mock.patch.object(Application, "getInstance", new = lambda: application_mock): + job = SendMaterialJob(device_mock) + job._onGetRemoteMaterials(reply_mock) + + self.assertEqual(0, device_mock.createFormPart.call_count) + + @patch("cura.Settings.CuraContainerRegistry") + @patch("UM.Application") + def test__onGetRemoteMaterials_withNoUpdate(self, application_mock, container_registry_mock, reply_mock, + device_mock): + application_mock.getContainerRegistry.return_value = container_registry_mock + + device_mock.createFormPart.return_value = "_xXx_" + + container_registry_mock.findContainersMetadata.return_value = [self._LOCAL_MATERIAL_WHITE] + + reply_mock.attribute.return_value = 200 + reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("ascii")) + + with mock.patch.object(Application, "getInstance", new = lambda: application_mock): + job = SendMaterialJob(device_mock) + job._onGetRemoteMaterials(reply_mock) + + self.assertEqual(0, device_mock.createFormPart.call_count) + self.assertEqual(0, device_mock.postFormWithParts.call_count) + + @patch("cura.Settings.CuraContainerRegistry") + @patch("UM.Application") + def test__onGetRemoteMaterials_withUpdatedMaterial(self, application_mock, container_registry_mock, reply_mock, + device_mock): + application_mock.getContainerRegistry.return_value = container_registry_mock + + device_mock.createFormPart.return_value = "_xXx_" + + localMaterialWhiteWithHigherVersion = self._LOCAL_MATERIAL_WHITE.copy() + localMaterialWhiteWithHigherVersion["version"] = "2" + container_registry_mock.findContainersMetadata.return_value = [localMaterialWhiteWithHigherVersion] + + reply_mock.attribute.return_value = 200 + reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("ascii")) + + with mock.patch.object(Application, "getInstance", new = lambda: application_mock): + job = SendMaterialJob(device_mock) + job._onGetRemoteMaterials(reply_mock) + + self.assertEqual(1, device_mock.createFormPart.call_count) + self.assertEqual(1, device_mock.postFormWithParts.call_count) + self.assertEquals( + [call.createFormPart("name=\"file\"; filename=\"generic_pla_white.xml.fdm_material\"", ""), + call.postFormWithParts(target = "materials/", parts = ["_xXx_"], on_finished = job.sendingFinished)], + device_mock.method_calls) + + @patch("cura.Settings.CuraContainerRegistry") + @patch("UM.Application") + def test__onGetRemoteMaterials_withNewMaterial(self, application_mock, container_registry_mock, reply_mock, + device_mock): + application_mock.getContainerRegistry.return_value = container_registry_mock + + device_mock.createFormPart.return_value = "_xXx_" + + container_registry_mock.findContainersMetadata.return_value = [self._LOCAL_MATERIAL_WHITE, + self._LOCAL_MATERIAL_BLACK] + + reply_mock.attribute.return_value = 200 + reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_BLACK]).encode("ascii")) + + with mock.patch.object(Application, "getInstance", new = lambda: application_mock): + job = SendMaterialJob(device_mock) + job._onGetRemoteMaterials(reply_mock) + + self.assertEqual(1, device_mock.createFormPart.call_count) + self.assertEqual(1, device_mock.postFormWithParts.call_count) + self.assertEquals( + [call.createFormPart("name=\"file\"; filename=\"generic_pla_white.xml.fdm_material\"", ""), + call.postFormWithParts(target = "materials/", parts = ["_xXx_"], on_finished = job.sendingFinished)], + device_mock.method_calls) diff --git a/plugins/USBPrinting/AutoDetectBaudJob.py b/plugins/USBPrinting/AutoDetectBaudJob.py index 8b37c4b29d..78de864e57 100644 --- a/plugins/USBPrinting/AutoDetectBaudJob.py +++ b/plugins/USBPrinting/AutoDetectBaudJob.py @@ -4,6 +4,7 @@ from UM.Job import Job from UM.Logger import Logger +from .avr_isp import ispBase from .avr_isp.stk500v2 import Stk500v2 from time import time, sleep @@ -14,12 +15,12 @@ from serial import Serial, SerialException # It tries a pre-set list of baud rates. All these baud rates are validated by requesting the temperature a few times # and checking if the results make sense. If getResult() is not None, it was able to find a correct baud rate. class AutoDetectBaudJob(Job): - def __init__(self, serial_port): + def __init__(self, serial_port: int) -> None: super().__init__() self._serial_port = serial_port self._all_baud_rates = [115200, 250000, 230400, 57600, 38400, 19200, 9600] - def run(self): + def run(self) -> None: Logger.log("d", "Auto detect baud rate started.") wait_response_timeouts = [3, 15, 30] wait_bootloader_times = [1.5, 5, 15] @@ -32,7 +33,7 @@ class AutoDetectBaudJob(Job): try: programmer.connect(self._serial_port) serial = programmer.leaveISP() - except: + except ispBase.IspError: programmer.close() for retry in range(tries): @@ -58,7 +59,7 @@ class AutoDetectBaudJob(Job): # We already have a serial connection, just change the baud rate. try: serial.baudrate = baud_rate - except: + except ValueError: continue sleep(wait_bootloader) # Ensure that we are not talking to the boot loader. 1.5 seconds seems to be the magic number successful_responses = 0 @@ -81,5 +82,5 @@ class AutoDetectBaudJob(Job): return serial.write(b"M105\n") - sleep(15) # Give the printer some time to init and try again. + sleep(15) # Give the printer some time to init and try again. self.setResult(None) # Unable to detect the correct baudrate. diff --git a/plugins/USBPrinting/plugin.json b/plugins/USBPrinting/plugin.json index 3484c8a48a..5d3cba8415 100644 --- a/plugins/USBPrinting/plugin.json +++ b/plugins/USBPrinting/plugin.json @@ -1,7 +1,7 @@ { "name": "USB printing", "author": "Ultimaker B.V.", - "version": "1.0.0", + "version": "1.0.1", "api": 5, "description": "Accepts G-Code and sends them to a printer. Plugin can also update firmware.", "i18n-catalog": "cura" diff --git a/resources/bundled_packages/cura.json b/resources/bundled_packages/cura.json index ee82b17a75..d8a7df2478 100644 --- a/resources/bundled_packages/cura.json +++ b/resources/bundled_packages/cura.json @@ -515,7 +515,7 @@ "package_type": "plugin", "display_name": "USB Printing", "description": "Accepts G-Code and sends them to a printer. Plugin can also update firmware.", - "package_version": "1.0.0", + "package_version": "1.0.1", "sdk_version": 5, "website": "https://ultimaker.com", "author": { diff --git a/resources/definitions/alfawise_u20.def.json b/resources/definitions/alfawise_u20.def.json index 87726fec3d..de8525fa4d 100644 --- a/resources/definitions/alfawise_u20.def.json +++ b/resources/definitions/alfawise_u20.def.json @@ -7,7 +7,7 @@ "author": "Samuel Pinches", "manufacturer": "Alfawise", "file_formats": "text/x-gcode", - "preferred_quality_type": "fine", + "preferred_quality_type": "fast", "machine_extruder_trains": { "0": "alfawise_u20_extruder_0" @@ -53,9 +53,6 @@ "material_bed_temperature": { "default_value": 50 }, - "layer_height": { - "default_value": 0.15 - }, "layer_height_0": { "default_value": 0.2 }, diff --git a/resources/definitions/bibo2_dual.def.json b/resources/definitions/bibo2_dual.def.json index e57fb64ec0..2036290ebd 100644 --- a/resources/definitions/bibo2_dual.def.json +++ b/resources/definitions/bibo2_dual.def.json @@ -37,9 +37,6 @@ "machine_heated_bed": { "default_value": true }, - "machine_nozzle_size": { - "default_value": 0.4 - }, "machine_nozzle_heat_up_speed": { "default_value": 2 }, @@ -66,9 +63,6 @@ ] ] }, - "material_diameter": { - "default_value": 1.75 - }, "gantry_height": { "default_value": 12 }, diff --git a/resources/definitions/bibo2_single_extruder_0.def.json b/resources/definitions/bibo2_single_extruder_0.def.json deleted file mode 100644 index 93c7a4e5ae..0000000000 --- a/resources/definitions/bibo2_single_extruder_0.def.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "id": "BIBO2 single E1", - "version": 2, - "name": "BIBO2 single E1", - "inherits": "fdmprinter", - "metadata": { - "visible": true, - "author": "na", - "manufacturer": "BIBO", - "category": "Other", - "file_formats": "text/x-gcode", - "has_materials": true, - "machine_extruder_trains": { - "0": "bibo2_single_extruder_0_0", - "1": "bibo2_single_extruder_0_1" - }, - "first_start_actions": [ - "MachineSettingsAction" - ] - }, - "overrides": { - "machine_name": { - "default_value": "BIBO2 single Extruder 1 (right)" - }, - "machine_width": { - "default_value": 214 - }, - "machine_height": { - "default_value": 160 - }, - "machine_depth": { - "default_value": 186 - }, - "machine_center_is_zero": { - "default_value": true - }, - "machine_heated_bed": { - "default_value": true - }, - "machine_nozzle_size": { - "default_value": 0.4 - }, - "machine_nozzle_heat_up_speed": { - "default_value": 2 - }, - "machine_nozzle_cool_down_speed": { - "default_value": 2 - }, - "machine_head_with_fans_polygon": { - "default_value": [ - [ - -68.18, - 64.63 - ], - [ - -68.18, - -47.38 - ], - [ - 35.18, - 64.63 - ], - [ - 35.18, - -47.38 - ] - ] - }, - "material_diameter": { - "default_value": 1.75 - }, - "gantry_height": { - "default_value": 12 - }, - "machine_use_extruder_offset_to_offset_coords": { - "default_value": true - }, - "machine_gcode_flavor": { - "default_value": "RepRap (Marlin/Sprinter)" - }, - "machine_start_gcode": { - "default_value": ";Startcode BIBO printers\nM109 T1 S170 ;preheat the other extruder, so it will not knock or ruin the print\nG90 ; absolute mode\nG21 ; metric values\nM82 ; Extruder in absolute mode\nM107\nG28\nG1 Z2 F400\nT0\nG90\nG92 E0\nG28\nG1 Y0 F1200 E0\nG92 E0\nG1 X-15.0 Y-92.9 Z0.3 F2400.0\t\t; move to start-line position\nG1 X15.0 Y-92.9 Z0.3 F1000.0 E2\t\t; draw 1st line\nG1 X15.0 Y-92.6 Z0.3 F3000.0\t\t; move to side a little\nG1 X-15.0 Y-92.6 Z0.3 F1000.0 E4\t\t; draw 2nd line\nG1 X-15.0 Y-92.3 Z0.3 F3000.0\t\t; move to side a little\nG1 X15.0 Y-92.3 Z0.3 F1000.0 E6\t\t; draw 3rd line\nG1 X15.0 Y-92 Z0.3 F3000.0\t\t; move to side a little\nG1 X-15.0 Y-92 Z0.3 F1000.0 E8\t\t; draw 4th line\nG1 X-16.0 Y-91.7 Z0.3 F3000.0\t\t; move to side a little\nG1 X16.0 Y-91.7 Z0.3 F1000.0 E10\t\t; draw 5th line\nG1 X16.0 Y-91.4 Z0.3 F3000.0\t\t; move to side a little\nG1 X-16.0 Y-91.4 Z0.3 F1000.0 E12\t\t; draw 5th line\nG1 E11.5 F2400\t\t\t\t; retract filament 0.5mm\nG92 E0\nM117 BIBO Printing..." - }, - "machine_end_gcode": { - "default_value": ";BIBO End GCode\nM107\nG91 ; Relative positioning\nG1 Z1 F100\nM104 T0 S0\nM104 T1 S0\nG1 X-20 Y-20 F3000\nG28 X0 Y0\nG90 ; Absolute positioning\nG92 E0 ; Reset extruder position\nM140 S0 ; Disable heated bed\nM84 ; Turn steppers off\nM117 BIBO Print complete\n " - }, - "machine_extruder_count": { - "default_value": 2 - }, - "prime_tower_position_x": { - "default_value": 50 - }, - "prime_tower_position_y": { - "default_value": 50 - } - } -} - diff --git a/resources/definitions/bibo2_single_extruder_1.def.json b/resources/definitions/bibo2_single_extruder_1.def.json deleted file mode 100644 index 246add09ab..0000000000 --- a/resources/definitions/bibo2_single_extruder_1.def.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "id": "BIBO2 single E2", - "version": 2, - "name": "BIBO2 single E2", - "inherits": "fdmprinter", - "metadata": { - "visible": true, - "author": "na", - "manufacturer": "BIBO", - "category": "Other", - "file_formats": "text/x-gcode", - "has_materials": true, - "machine_extruder_trains": { - "0": "bibo2_single_extruder_1_0", - "1": "bibo2_single_extruder_1_1" - }, - "first_start_actions": [ - "MachineSettingsAction" - ] - }, - "overrides": { - "machine_name": { - "default_value": "BIBO2 single Extruder 2 (left)" - }, - "machine_width": { - "default_value": 214 - }, - "machine_height": { - "default_value": 160 - }, - "machine_depth": { - "default_value": 186 - }, - "machine_center_is_zero": { - "default_value": true - }, - "machine_heated_bed": { - "default_value": true - }, - "machine_nozzle_size": { - "default_value": 0.4 - }, - "machine_nozzle_heat_up_speed": { - "default_value": 2 - }, - "machine_nozzle_cool_down_speed": { - "default_value": 2 - }, - "machine_head_with_fans_polygon": { - "default_value": [ - [ - -68.18, - 64.63 - ], - [ - -68.18, - -47.38 - ], - [ - 35.18, - 64.63 - ], - [ - 35.18, - -47.38 - ] - ] - }, - "material_diameter": { - "default_value": 1.75 - }, - "gantry_height": { - "default_value": 12 - }, - "machine_use_extruder_offset_to_offset_coords": { - "default_value": true - }, - "machine_gcode_flavor": { - "default_value": "RepRap (Marlin/Sprinter)" - }, - "machine_start_gcode": { - "default_value": ";Startcode BIBO printers\nM109 T0 S170 ;preheat the other extruder, so it will not knock or ruin the print\nG90 ; absolute mode\nG21 ; metric values\nM82 ; Extruder in absolute mode\nM107\nG28\nG1 Z2 F400\nT0\nG90\nG92 E0\nG28\nG1 Y0 F1200 E0\nG92 E0\nT1\nG92 E0\nG1 X-15.0 Y-92.9 Z0.3 F2400.0\t\t; move to start-line position\nG1 X15.0 Y-92.9 Z0.3 F1000.0 E2\t\t; draw 1st line\nG1 X15.0 Y-92.6 Z0.3 F3000.0\t\t; move to side a little\nG1 X-15.0 Y-92.6 Z0.3 F1000.0 E4\t\t; draw 2nd line\nG1 X-15.0 Y-92.3 Z0.3 F3000.0\t\t; move to side a little\nG1 X15.0 Y-92.3 Z0.3 F1000.0 E6\t\t; draw 3rd line\nG1 X15.0 Y-92 Z0.3 F3000.0\t\t; move to side a little\nG1 X-15.0 Y-92 Z0.3 F1000.0 E8\t\t; draw 4th line\nG1 X-16.0 Y-91.7 Z0.3 F3000.0\t\t; move to side a little\nG1 X16.0 Y-91.7 Z0.3 F1000.0 E10\t\t; draw 5th line\nG1 X16.0 Y-91.4 Z0.3 F3000.0\t\t; move to side a little\nG1 X-16.0 Y-91.4 Z0.3 F1000.0 E12\t\t; draw 5th line\nG1 E11.5 F2400\t\t\t\t; retract filament 0.5mm\nG92 E0\nM117 BIBO Printing..." - }, - "machine_end_gcode": { - "default_value": ";BIBO End GCode\nM107\nG91 ; Relative positioning\nG1 Z1 F100\nM104 T0 S0\nM104 T1 S0\nG1 X-20 Y-20 F3000\nG28 X0 Y0\nG90 ; Absolute positioning\nG92 E0 ; Reset extruder position\nM140 S0 ; Disable heated bed\nM84 ; Turn steppers off\nM117 BIBO Print complete\n " - }, - "machine_extruder_count": { - "default_value": 2 - }, - "prime_tower_position_x": { - "default_value": 50 - }, - "prime_tower_position_y": { - "default_value": 50 - } - } -} - diff --git a/resources/definitions/cocoon_create_modelmaker.def.json b/resources/definitions/cocoon_create_modelmaker.def.json index 204d5b9492..22aa75d09e 100644 --- a/resources/definitions/cocoon_create_modelmaker.def.json +++ b/resources/definitions/cocoon_create_modelmaker.def.json @@ -1,11 +1,11 @@ { - "name": "Cocoon Create ModelMaker & Wanhao Duplicator i3 Mini", + "name": "Cocoon Create ModelMaker", "version": 2, "inherits": "fdmprinter", "metadata": { "visible": true, "author": "Samuel Pinches", - "manufacturer": "Cocoon Create / Wanhao", + "manufacturer": "Cocoon Create", "file_formats": "text/x-gcode", "preferred_quality_type": "fine", "machine_extruder_trains": @@ -15,7 +15,7 @@ }, "overrides": { "machine_name": { - "default_value": "Cocoon Create ModelMaker & Wanhao Duplicator i3 Mini" + "default_value": "Cocoon Create ModelMaker" }, "machine_start_gcode": { "default_value": "; -- START GCODE --\nG21 ;set units to millimetres\nG90 ;set to absolute positioning\nM106 S0 ;set fan speed to zero (turned off)\nG28 ;home all axis\nG92 E0 ;zero the extruded length\nG1 Z1 F1000 ;move up slightly\nG1 X60.0 Z0 E9.0 F1000.0;intro line\nG1 X100.0 E21.5 F1000.0 ;continue line\nG92 E0 ;zero the extruded length again\n; -- end of START GCODE --" @@ -51,7 +51,7 @@ "default_value": 220 }, "layer_height": { - "default_value": 0.15 + "default_value": 0.10 }, "layer_height_0": { "default_value": 0.2 diff --git a/resources/definitions/jgaurora_a1.def.json b/resources/definitions/jgaurora_a1.def.json index 4fd2eb4994..b9a921c311 100644 --- a/resources/definitions/jgaurora_a1.def.json +++ b/resources/definitions/jgaurora_a1.def.json @@ -7,7 +7,7 @@ "author": "Samuel Pinches", "manufacturer": "JGAurora", "file_formats": "text/x-gcode", - "preferred_quality_type": "fine", + "preferred_quality_type": "fast", "machine_extruder_trains": { "0": "jgaurora_a1_extruder_0" @@ -53,9 +53,6 @@ "material_bed_temperature": { "default_value": 67 }, - "layer_height": { - "default_value": 0.15 - }, "layer_height_0": { "default_value": 0.12 }, diff --git a/resources/definitions/jgaurora_a5.def.json b/resources/definitions/jgaurora_a5.def.json index 02d9a9db4f..d84a8440e6 100644 --- a/resources/definitions/jgaurora_a5.def.json +++ b/resources/definitions/jgaurora_a5.def.json @@ -9,7 +9,7 @@ "file_formats": "text/x-gcode", "platform": "jgaurora_a5.stl", "platform_offset": [-242, -101, 273], - "preferred_quality_type": "fine", + "preferred_quality_type": "fast", "machine_extruder_trains": { "0": "jgaurora_a5_extruder_0" @@ -55,9 +55,6 @@ "material_bed_temperature": { "default_value": 67 }, - "layer_height": { - "default_value": 0.15 - }, "layer_height_0": { "default_value": 0.12 }, diff --git a/resources/definitions/jgaurora_z_603s.def.json b/resources/definitions/jgaurora_z_603s.def.json index 59e0ff129c..3a78585240 100644 --- a/resources/definitions/jgaurora_z_603s.def.json +++ b/resources/definitions/jgaurora_z_603s.def.json @@ -7,7 +7,7 @@ "author": "Samuel Pinches", "manufacturer": "JGAurora", "file_formats": "text/x-gcode", - "preferred_quality_type": "fine", + "preferred_quality_type": "fast", "machine_extruder_trains": { "0": "jgaurora_z_603s_extruder_0" @@ -53,9 +53,6 @@ "material_bed_temperature": { "default_value": 55 }, - "layer_height": { - "default_value": 0.15 - }, "layer_height_0": { "default_value": 0.2 }, diff --git a/resources/extruders/bibo2_dual_extruder_0.def.json b/resources/extruders/bibo2_dual_extruder_0.def.json index 7cdc03d504..f83801fa0c 100644 --- a/resources/extruders/bibo2_dual_extruder_0.def.json +++ b/resources/extruders/bibo2_dual_extruder_0.def.json @@ -12,6 +12,12 @@ "default_value": 0, "maximum_value": "1" }, + "material_diameter": { + "default_value": 1.75 + }, + "machine_nozzle_size": { + "default_value": 0.4 + }, "machine_nozzle_offset_x": { "default_value": 0.0 }, diff --git a/resources/extruders/bibo2_dual_extruder_1.def.json b/resources/extruders/bibo2_dual_extruder_1.def.json index daa1504220..5f479ba54b 100644 --- a/resources/extruders/bibo2_dual_extruder_1.def.json +++ b/resources/extruders/bibo2_dual_extruder_1.def.json @@ -12,11 +12,17 @@ "default_value": 1, "maximum_value": "1" }, + "material_diameter": { + "default_value": 1.75 + }, + "machine_nozzle_size": { + "default_value": 0.4 + }, "machine_nozzle_offset_x": { - "default_value": 0 + "default_value": 0.0 }, "machine_nozzle_offset_y": { - "default_value": 0 + "default_value": 0.0 }, "machine_extruder_start_pos_abs": { "default_value": true diff --git a/resources/extruders/bibo2_single_extruder_0_0.def.json b/resources/extruders/bibo2_single_extruder_0_0.def.json deleted file mode 100644 index 7d0b246131..0000000000 --- a/resources/extruders/bibo2_single_extruder_0_0.def.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "id": "BIBO2 E1a", - "version": 2, - "name": "BIBO2 E1", - "inherits": "fdmextruder", - "metadata": { - "machine": "BIBO2 single E1", - "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 - }, - "machine_extruder_start_pos_abs": { - "default_value": true - }, - "machine_extruder_start_pos_x": { - "value": "prime_tower_position_x" - }, - "machine_extruder_start_pos_y": { - "value": "prime_tower_position_y" - }, - "machine_extruder_end_pos_abs": { - "default_value": true - }, - "machine_extruder_end_pos_x": { - "value": "prime_tower_position_x" - }, - "machine_extruder_end_pos_y": { - "value": "prime_tower_position_y" - } - } -} diff --git a/resources/extruders/bibo2_single_extruder_0_1.def.json b/resources/extruders/bibo2_single_extruder_0_1.def.json deleted file mode 100644 index 76187696fc..0000000000 --- a/resources/extruders/bibo2_single_extruder_0_1.def.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "id": "BIBO2 E1b", - "version": 2, - "name": "E2 not used", - "inherits": "fdmextruder", - "metadata": { - "machine": "BIBO2 single E1", - "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 - }, - "machine_extruder_start_pos_abs": { - "default_value": true - }, - "machine_extruder_start_pos_x": { - "value": "prime_tower_position_x" - }, - "machine_extruder_start_pos_y": { - "value": "prime_tower_position_y" - }, - "machine_extruder_end_pos_abs": { - "default_value": true - }, - "machine_extruder_end_pos_x": { - "value": "prime_tower_position_x" - }, - "machine_extruder_end_pos_y": { - "value": "prime_tower_position_y" - } - } -} diff --git a/resources/extruders/bibo2_single_extruder_1_0.def.json b/resources/extruders/bibo2_single_extruder_1_0.def.json deleted file mode 100644 index 3cf667de82..0000000000 --- a/resources/extruders/bibo2_single_extruder_1_0.def.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "id": "BIBO2 E2a", - "version": 2, - "name": "E1 not used", - "inherits": "fdmextruder", - "metadata": { - "machine": "BIBO2 single E2", - "position": "0" - }, - "overrides": { - "extruder_nr": { - "default_value": 0, - "maximum_value": "1" - }, - "machine_nozzle_offset_x": { - "default_value": 0 - }, - "machine_nozzle_offset_y": { - "default_value": 0 - }, - "machine_extruder_start_pos_abs": { - "default_value": true - }, - "machine_extruder_start_pos_x": { - "value": "prime_tower_position_x" - }, - "machine_extruder_start_pos_y": { - "value": "prime_tower_position_y" - }, - "machine_extruder_end_pos_abs": { - "default_value": true - }, - "machine_extruder_end_pos_x": { - "value": "prime_tower_position_x" - }, - "machine_extruder_end_pos_y": { - "value": "prime_tower_position_y" - } - } -} diff --git a/resources/extruders/bibo2_single_extruder_1_1.def.json b/resources/extruders/bibo2_single_extruder_1_1.def.json deleted file mode 100644 index e8f3ec7054..0000000000 --- a/resources/extruders/bibo2_single_extruder_1_1.def.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "id": "BIBO2 E2b", - "version": 2, - "name": "BIBO2 E2", - "inherits": "fdmextruder", - "metadata": { - "machine": "BIBO2 single E2", - "position": "1" - }, - "overrides": { - "extruder_nr": { - "default_value": 1, - "maximum_value": "1" - }, - "machine_nozzle_offset_x": { - "default_value": 0 - }, - "machine_nozzle_offset_y": { - "default_value": 0 - }, - "machine_extruder_start_pos_abs": { - "default_value": true - }, - "machine_extruder_start_pos_x": { - "value": "prime_tower_position_x" - }, - "machine_extruder_start_pos_y": { - "value": "prime_tower_position_y" - }, - "machine_extruder_end_pos_abs": { - "default_value": true - }, - "machine_extruder_end_pos_x": { - "value": "prime_tower_position_x" - }, - "machine_extruder_end_pos_y": { - "value": "prime_tower_position_y" - } - } -} diff --git a/resources/qml/AddMachineDialog.qml b/resources/qml/AddMachineDialog.qml index aa160acd4d..2bbdba319c 100644 --- a/resources/qml/AddMachineDialog.qml +++ b/resources/qml/AddMachineDialog.qml @@ -213,28 +213,6 @@ UM.Dialog PropertyChanges { target: machineButton; opacity: 0; height: 0; } } - - transitions: - [ - Transition - { - to: "collapsed"; - SequentialAnimation - { - NumberAnimation { property: "opacity"; duration: 75; } - NumberAnimation { property: "height"; duration: 75; } - } - }, - Transition - { - from: "collapsed"; - SequentialAnimation - { - NumberAnimation { property: "height"; duration: 75; } - NumberAnimation { property: "opacity"; duration: 75; } - } - } - ] } } } diff --git a/resources/qml/Menus/ProfileMenu.qml b/resources/qml/Menus/ProfileMenu.qml index ffd3c556b6..fd46d2ef72 100644 --- a/resources/qml/Menus/ProfileMenu.qml +++ b/resources/qml/Menus/ProfileMenu.qml @@ -17,18 +17,21 @@ Menu MenuItem { - text: (model.layer_height != "") ? model.name + " - " + model.layer_height + model.layer_height_unit : model.name + text: + { + var full_text = (model.layer_height != "") ? model.name + " - " + model.layer_height + model.layer_height_unit : model.name + full_text += model.is_experimental ? " - Experimental" : "" + return full_text + } checkable: true checked: Cura.MachineManager.activeQualityOrQualityChangesName == model.name exclusiveGroup: group - onTriggered: { - Cura.MachineManager.setQualityGroup(model.quality_group) - } + onTriggered: Cura.MachineManager.setQualityGroup(model.quality_group) visible: model.available } - onObjectAdded: menu.insertItem(index, object); - onObjectRemoved: menu.removeItem(object); + onObjectAdded: menu.insertItem(index, object) + onObjectRemoved: menu.removeItem(object) } MenuSeparator diff --git a/resources/qml/Preferences/GeneralPage.qml b/resources/qml/Preferences/GeneralPage.qml index bba2cf764a..5006ae2777 100644 --- a/resources/qml/Preferences/GeneralPage.qml +++ b/resources/qml/Preferences/GeneralPage.qml @@ -151,7 +151,6 @@ UM.PreferencesPage { id: languageLabel text: catalog.i18nc("@label","Language:") - anchors.verticalCenter: languageComboBox.verticalCenter } ComboBox @@ -219,7 +218,6 @@ UM.PreferencesPage { id: currencyLabel text: catalog.i18nc("@label","Currency:") - anchors.verticalCenter: currencyField.verticalCenter } TextField @@ -233,7 +231,6 @@ UM.PreferencesPage { id: themeLabel text: catalog.i18nc("@label","Theme:") - anchors.verticalCenter: themeComboBox.verticalCenter } ComboBox diff --git a/resources/qml/Preferences/SettingVisibilityPage.qml b/resources/qml/Preferences/SettingVisibilityPage.qml index 8896d0611e..2edbeee960 100644 --- a/resources/qml/Preferences/SettingVisibilityPage.qml +++ b/resources/qml/Preferences/SettingVisibilityPage.qml @@ -25,11 +25,7 @@ UM.PreferencesPage function reset() { - UM.Preferences.resetPreference("general/visible_settings") - - // After calling this function update Setting visibility preset combobox. - // Reset should set default setting preset ("Basic") - visibilityPreset.currentIndex = 1 + settingVisibilityPresetsModel.setActivePreset("basic") } resetEnabled: true; @@ -115,21 +111,22 @@ UM.PreferencesPage currentIndex: { + var idx = -1; for(var i = 0; i < settingVisibilityPresetsModel.items.length; ++i) { - if(settingVisibilityPresetsModel.items[i].id == settingVisibilityPresetsModel.activePreset) + if(settingVisibilityPresetsModel.items[i].presetId == settingVisibilityPresetsModel.activePreset) { - currentIndex = i; - return; + idx = i; + break; } } - return -1 + return idx; } onActivated: { - var preset_id = settingVisibilityPresetsModel.items[index].id; - settingVisibilityPresetsModel.setActivePreset(preset_id); + var preset_id = settingVisibilityPresetsModel.items[index].presetId + settingVisibilityPresetsModel.setActivePreset(preset_id) } } diff --git a/resources/qml/Settings/SettingView.qml b/resources/qml/Settings/SettingView.qml index da50b430ac..5d79eef249 100644 --- a/resources/qml/Settings/SettingView.qml +++ b/resources/qml/Settings/SettingView.qml @@ -64,11 +64,18 @@ Item activeFocusOnPress: true menu: ProfileMenu { } - function generateActiveQualityText () { - var result = Cura.MachineManager.activeQualityOrQualityChangesName; + function generateActiveQualityText () + { + var result = Cura.MachineManager.activeQualityOrQualityChangesName + if (Cura.MachineManager.isActiveQualityExperimental) + { + result += " (Experimental)" + } - if (Cura.MachineManager.isActiveQualitySupported) { - if (Cura.MachineManager.activeQualityLayerHeight > 0) { + if (Cura.MachineManager.isActiveQualitySupported) + { + if (Cura.MachineManager.activeQualityLayerHeight > 0) + { result += " " result += " - " result += Cura.MachineManager.activeQualityLayerHeight + "mm" diff --git a/resources/quality/anycubic_i3_mega/anycubic_i3_mega_draft.inst.cfg b/resources/quality/anycubic_i3_mega/anycubic_i3_mega_draft.inst.cfg index bb47f68574..e94b9f01d1 100644 --- a/resources/quality/anycubic_i3_mega/anycubic_i3_mega_draft.inst.cfg +++ b/resources/quality/anycubic_i3_mega/anycubic_i3_mega_draft.inst.cfg @@ -8,6 +8,7 @@ setting_version = 5 type = quality quality_type = draft weight = 0 +global_quality = True [values] acceleration_enabled = True diff --git a/resources/quality/anycubic_i3_mega/anycubic_i3_mega_high.inst.cfg b/resources/quality/anycubic_i3_mega/anycubic_i3_mega_high.inst.cfg index a3ae98deba..c8c4bf9a81 100644 --- a/resources/quality/anycubic_i3_mega/anycubic_i3_mega_high.inst.cfg +++ b/resources/quality/anycubic_i3_mega/anycubic_i3_mega_high.inst.cfg @@ -8,6 +8,7 @@ setting_version = 5 type = quality quality_type = high weight = 2 +global_quality = True [values] acceleration_enabled = True diff --git a/resources/quality/anycubic_i3_mega/anycubic_i3_mega_normal.inst.cfg b/resources/quality/anycubic_i3_mega/anycubic_i3_mega_normal.inst.cfg index 13846b9702..399c3ebc55 100644 --- a/resources/quality/anycubic_i3_mega/anycubic_i3_mega_normal.inst.cfg +++ b/resources/quality/anycubic_i3_mega/anycubic_i3_mega_normal.inst.cfg @@ -8,6 +8,7 @@ setting_version = 5 type = quality quality_type = normal weight = 1 +global_quality = True [values] acceleration_enabled = True diff --git a/resources/quality/ultimaker3/um3_aa0.25_PC_Normal_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.25_PC_Normal_Quality.inst.cfg index e08fa27dc9..4d585b54c1 100644 --- a/resources/quality/ultimaker3/um3_aa0.25_PC_Normal_Quality.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.25_PC_Normal_Quality.inst.cfg @@ -10,6 +10,7 @@ quality_type = normal weight = 0 material = generic_pc variant = AA 0.25 +is_experimental = True [values] acceleration_enabled = True diff --git a/resources/quality/ultimaker3/um3_aa0.25_PP_Normal_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.25_PP_Normal_Quality.inst.cfg index 6e9bbdce27..bee345e302 100644 --- a/resources/quality/ultimaker3/um3_aa0.25_PP_Normal_Quality.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.25_PP_Normal_Quality.inst.cfg @@ -10,6 +10,7 @@ quality_type = normal weight = 0 material = generic_pp variant = AA 0.25 +is_experimental = True [values] acceleration_enabled = True diff --git a/resources/quality/ultimaker3/um3_aa0.8_CPEP_Fast_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_CPEP_Fast_Print.inst.cfg index 73df9637f7..fc4acf3cb0 100644 --- a/resources/quality/ultimaker3/um3_aa0.8_CPEP_Fast_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.8_CPEP_Fast_Print.inst.cfg @@ -10,6 +10,7 @@ quality_type = draft weight = -2 material = generic_cpe_plus variant = AA 0.8 +is_experimental = True [values] brim_width = 14 diff --git a/resources/quality/ultimaker3/um3_aa0.8_CPEP_Superdraft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_CPEP_Superdraft_Print.inst.cfg index d59bfe7cea..36b3ef603f 100644 --- a/resources/quality/ultimaker3/um3_aa0.8_CPEP_Superdraft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.8_CPEP_Superdraft_Print.inst.cfg @@ -10,6 +10,7 @@ quality_type = superdraft weight = -4 material = generic_cpe_plus variant = AA 0.8 +is_experimental = True [values] brim_width = 14 diff --git a/resources/quality/ultimaker3/um3_aa0.8_CPEP_Verydraft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_CPEP_Verydraft_Print.inst.cfg index 368317019f..14e08cb14d 100644 --- a/resources/quality/ultimaker3/um3_aa0.8_CPEP_Verydraft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.8_CPEP_Verydraft_Print.inst.cfg @@ -10,6 +10,7 @@ quality_type = verydraft weight = -3 material = generic_cpe_plus variant = AA 0.8 +is_experimental = True [values] brim_width = 14 diff --git a/resources/quality/ultimaker3/um3_aa0.8_PC_Fast_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_PC_Fast_Print.inst.cfg index 5b81532977..c2bb6d4988 100644 --- a/resources/quality/ultimaker3/um3_aa0.8_PC_Fast_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.8_PC_Fast_Print.inst.cfg @@ -10,6 +10,7 @@ quality_type = draft weight = 0 material = generic_pc variant = AA 0.8 +is_experimental = True [values] brim_width = 14 diff --git a/resources/quality/ultimaker3/um3_aa0.8_PC_Superdraft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_PC_Superdraft_Print.inst.cfg index 317b89ea85..e815b673d1 100644 --- a/resources/quality/ultimaker3/um3_aa0.8_PC_Superdraft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.8_PC_Superdraft_Print.inst.cfg @@ -10,6 +10,7 @@ quality_type = superdraft weight = -2 material = generic_pc variant = AA 0.8 +is_experimental = True [values] brim_width = 14 diff --git a/resources/quality/ultimaker3/um3_aa0.8_PC_Verydraft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_PC_Verydraft_Print.inst.cfg index 2fd6bd7609..c50cee576d 100644 --- a/resources/quality/ultimaker3/um3_aa0.8_PC_Verydraft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.8_PC_Verydraft_Print.inst.cfg @@ -10,6 +10,7 @@ quality_type = verydraft weight = -1 material = generic_pc variant = AA 0.8 +is_experimental = True [values] brim_width = 14 diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.25_PC_Normal_Quality.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.25_PC_Normal_Quality.inst.cfg index 55d53c6c71..53c319d6e6 100644 --- a/resources/quality/ultimaker_s5/um_s5_aa0.25_PC_Normal_Quality.inst.cfg +++ b/resources/quality/ultimaker_s5/um_s5_aa0.25_PC_Normal_Quality.inst.cfg @@ -10,6 +10,7 @@ quality_type = normal weight = 0 material = generic_pc variant = AA 0.25 +is_experimental = True [values] acceleration_enabled = True diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.25_PP_Normal_Quality.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.25_PP_Normal_Quality.inst.cfg index c925845dc1..b4d34cc392 100644 --- a/resources/quality/ultimaker_s5/um_s5_aa0.25_PP_Normal_Quality.inst.cfg +++ b/resources/quality/ultimaker_s5/um_s5_aa0.25_PP_Normal_Quality.inst.cfg @@ -10,6 +10,7 @@ quality_type = normal weight = 0 material = generic_pp variant = AA 0.25 +is_experimental = True [values] acceleration_enabled = True diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.8_CPEP_Fast_Print.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.8_CPEP_Fast_Print.inst.cfg index e78006689b..4bdd09e9b3 100644 --- a/resources/quality/ultimaker_s5/um_s5_aa0.8_CPEP_Fast_Print.inst.cfg +++ b/resources/quality/ultimaker_s5/um_s5_aa0.8_CPEP_Fast_Print.inst.cfg @@ -10,6 +10,7 @@ quality_type = draft weight = -2 material = generic_cpe_plus variant = AA 0.8 +is_experimental = True [values] brim_width = 14 diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.8_CPEP_Superdraft_Print.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.8_CPEP_Superdraft_Print.inst.cfg index c6d0962157..f9b6b0c7a6 100644 --- a/resources/quality/ultimaker_s5/um_s5_aa0.8_CPEP_Superdraft_Print.inst.cfg +++ b/resources/quality/ultimaker_s5/um_s5_aa0.8_CPEP_Superdraft_Print.inst.cfg @@ -10,6 +10,7 @@ quality_type = superdraft weight = -4 material = generic_cpe_plus variant = AA 0.8 +is_experimental = True [values] brim_width = 14 diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.8_CPEP_Verydraft_Print.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.8_CPEP_Verydraft_Print.inst.cfg index b80f773594..9c6c7de7f0 100644 --- a/resources/quality/ultimaker_s5/um_s5_aa0.8_CPEP_Verydraft_Print.inst.cfg +++ b/resources/quality/ultimaker_s5/um_s5_aa0.8_CPEP_Verydraft_Print.inst.cfg @@ -10,6 +10,7 @@ quality_type = verydraft weight = -3 material = generic_cpe_plus variant = AA 0.8 +is_experimental = True [values] brim_width = 14 diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.8_PC_Fast_Print.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.8_PC_Fast_Print.inst.cfg index 0ed4e3d994..c24aa9a98d 100644 --- a/resources/quality/ultimaker_s5/um_s5_aa0.8_PC_Fast_Print.inst.cfg +++ b/resources/quality/ultimaker_s5/um_s5_aa0.8_PC_Fast_Print.inst.cfg @@ -10,6 +10,7 @@ quality_type = draft weight = 0 material = generic_pc variant = AA 0.8 +is_experimental = True [values] brim_width = 14 diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.8_PC_Superdraft_Print.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.8_PC_Superdraft_Print.inst.cfg index 53bf1d3107..5fc306b1f1 100644 --- a/resources/quality/ultimaker_s5/um_s5_aa0.8_PC_Superdraft_Print.inst.cfg +++ b/resources/quality/ultimaker_s5/um_s5_aa0.8_PC_Superdraft_Print.inst.cfg @@ -10,6 +10,7 @@ quality_type = superdraft weight = -2 material = generic_pc variant = AA 0.8 +is_experimental = True [values] brim_width = 14 diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.8_PC_Verydraft_Print.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.8_PC_Verydraft_Print.inst.cfg index d9c45c2634..996adf5be7 100644 --- a/resources/quality/ultimaker_s5/um_s5_aa0.8_PC_Verydraft_Print.inst.cfg +++ b/resources/quality/ultimaker_s5/um_s5_aa0.8_PC_Verydraft_Print.inst.cfg @@ -10,6 +10,7 @@ quality_type = verydraft weight = -1 material = generic_pc variant = AA 0.8 +is_experimental = True [values] brim_width = 14 diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_CPEP_Fast_Print.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_CPEP_Fast_Print.inst.cfg index 3e74390840..4098c86ad9 100644 --- a/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_CPEP_Fast_Print.inst.cfg +++ b/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_CPEP_Fast_Print.inst.cfg @@ -11,6 +11,7 @@ weight = -2 material = generic_cpe_plus variant = AA 0.8 buildplate = Aluminum +is_experimental = True [values] brim_width = 14 diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_PC_Fast_Print.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_PC_Fast_Print.inst.cfg index 747e2fe8a5..6fdd22c6b0 100644 --- a/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_PC_Fast_Print.inst.cfg +++ b/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_PC_Fast_Print.inst.cfg @@ -11,6 +11,7 @@ weight = 0 material = generic_pc variant = AA 0.8 buildplate = Aluminum +is_experimental = True [values] brim_width = 10