diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000000..07a53c47c7 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,116 @@ +# Copyright (c) 2019 Ultimaker B.V. +# This file contains the Pylint rules used in the stardust projects. + +# To configure PyLint as an external tool in PyCharm, create a new External Tool with the settings: +# +# Name: PyLint +# Program: Check with 'which pylint'. For example: ~/.local/bin/pylint +# Arguments: $FileDirName$ --rcfile=.pylintrc --msg-template='{abspath}:{line}:{column}:({symbol}):{msg_id}:{msg}' +# Working directory: $ContentRoot$ +# Output filters: $FILE_PATH$:$LINE$:$COLUMN$:.* +# +# You can add a keyboard shortcut in the keymap settings. To run Pylint to a project, select the module +# you want to check (e.g. cura folder) before running the external tool. +# +# If you find a better way to configure the external tool please edit this file. + +[MASTER] +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins=pylint_quotes + +# We expect double string quotes +string-quote=double-avoid-escape + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Add files or directories to the blacklist. They should be base names, not paths. +ignore=tests + +[REFACTORING] +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +[MESSAGES CONTROL] +# C0326: No space allowed around keyword argument assignment +# C0411: Ignore import order because the rules are different than in PyCharm, so automatic imports break lots of builds +# C0412: Ignore import order because the rules are different than in PyCharm, so automatic imports break lots of builds +# C0413: Ignore import order because the rules are different than in PyCharm, so automatic imports break lots of builds +# R0201: Method could be a function (no-self-use) +# R0401: Cyclic imports (cyclic-import) are used for typing +# R0801: Unfortunately the error is triggered for a lot of similar models (duplicate-code) +# R1710: Either all return statements in a function should return an expression, or none of them should. +# W0221: Parameters differ from overridden method (tornado http methods have a flexible number of parameters) +# W0511: Ignore warnings generated for TODOs in the code +# C0111: We don't use docstring +# C0303: Trailing whitespace isn't something we care about +# C4001: You can put " in a string if you escape it first... +disable=C0326,C0411,C0412,C0413,R0201,R0401,R0801,R1710,W0221,W0511, C0111, C0303,C4001 + +[FORMAT] +# Maximum number of characters on a single line. +max-line-length=120 + +# Maximum number of lines in a module. +max-module-lines=500 + +good-names=os + +[BASIC] +# allow modules and functions to use PascalCase +module-rgx=[a-zA-Z0-9_]+$ +function-rgx= +## Allowed methods: +# getSomething +# _getSomething +# __getSomething +# __new__ +## Disallowed: +# _GET +# GetSomething +method-rgx=(_{,2}[a-z][A-Za-z0-9]*_{,2})$ + +[DESIGN] +# Maximum number of arguments for function / method. +max-args=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=8 + +# Maximum number of boolean expressions in an if statement. +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (R0903). +# We set this to 0 because our models and fields do not have methods. +min-public-methods=0 + +ignored-argument-names=arg|args|kwargs|_ + +[CLASSES] +defining-attr-methods=__init__,__new__,setUp,initialize + +[TYPECHECK] +ignored-classes=NotImplemented + +[VARIABLES] +dummy-variables-rgx=_+[a-z0-9_]{2,30} diff --git a/cura/Arranging/Arrange.py b/cura/Arranging/Arrange.py index caa7aae910..c0aca9a893 100644 --- a/cura/Arranging/Arrange.py +++ b/cura/Arranging/Arrange.py @@ -1,6 +1,6 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import List +from typing import List, Optional from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Logger import Logger @@ -8,6 +8,7 @@ from UM.Math.Polygon import Polygon from UM.Math.Vector import Vector from UM.Scene.SceneNode import SceneNode from cura.Arranging.ShapeArray import ShapeArray +from cura.BuildVolume import BuildVolume from cura.Scene import ZOffsetDecorator from collections import namedtuple @@ -27,7 +28,7 @@ LocationSuggestion = namedtuple("LocationSuggestion", ["x", "y", "penalty_points # # Note: Make sure the scale is the same between ShapeArray objects and the Arrange instance. class Arrange: - build_volume = None + build_volume = None # type: Optional[BuildVolume] def __init__(self, x, y, offset_x, offset_y, scale= 0.5): self._scale = scale # convert input coordinates to arrange coordinates @@ -68,7 +69,7 @@ class Arrange: points = copy.deepcopy(vertices._points) # After scaling (like up to 0.1 mm) the node might not have points - if len(points) == 0: + if not points.size: continue shape_arr = ShapeArray.fromPolygon(points, scale = scale) @@ -113,7 +114,7 @@ class Arrange: found_spot = True self.place(x, y, offset_shape_arr) # place the object in arranger else: - Logger.log("d", "Could not find spot!"), + Logger.log("d", "Could not find spot!") found_spot = False node.setPosition(Vector(200, center_y, 100)) return found_spot diff --git a/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py b/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py index 89f613e180..7736efbeeb 100644 --- a/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py +++ b/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py @@ -29,7 +29,7 @@ class ArrangeArray: self._has_empty = False self._arrange = [] # type: List[Arrange] - def _update_first_empty(self): + def _updateFirstEmpty(self): for i, a in enumerate(self._arrange): if a.isEmpty: self._first_empty = i @@ -42,7 +42,7 @@ class ArrangeArray: new_arrange = Arrange.create(x = self._x, y = self._y, fixed_nodes = self._fixed_nodes) self._arrange.append(new_arrange) self._count += 1 - self._update_first_empty() + self._updateFirstEmpty() def count(self): return self._count diff --git a/cura/AutoSave.py b/cura/AutoSave.py index 3b42fdafdf..2c1dbe4a84 100644 --- a/cura/AutoSave.py +++ b/cura/AutoSave.py @@ -2,12 +2,16 @@ # Cura is released under the terms of the LGPLv3 or higher. from PyQt5.QtCore import QTimer +from typing import Any, TYPE_CHECKING from UM.Logger import Logger +if TYPE_CHECKING: + from cura.CuraApplication import CuraApplication + class AutoSave: - def __init__(self, application): + def __init__(self, application: "CuraApplication") -> None: self._application = application self._application.getPreferences().preferenceChanged.connect(self._triggerTimer) @@ -22,14 +26,14 @@ class AutoSave: self._enabled = True self._saving = False - def initialize(self): + def initialize(self) -> None: # only initialise if the application is created and has started self._change_timer.timeout.connect(self._onTimeout) self._application.globalContainerStackChanged.connect(self._onGlobalStackChanged) self._onGlobalStackChanged() self._triggerTimer() - def _triggerTimer(self, *args): + def _triggerTimer(self, *args: Any) -> None: if not self._saving: self._change_timer.start() @@ -40,7 +44,7 @@ class AutoSave: else: self._change_timer.stop() - def _onGlobalStackChanged(self): + def _onGlobalStackChanged(self) -> None: if self._global_stack: self._global_stack.propertyChanged.disconnect(self._triggerTimer) self._global_stack.containersChanged.disconnect(self._triggerTimer) @@ -51,7 +55,7 @@ class AutoSave: self._global_stack.propertyChanged.connect(self._triggerTimer) self._global_stack.containersChanged.connect(self._triggerTimer) - def _onTimeout(self): + def _onTimeout(self) -> None: self._saving = True # To prevent the save process from triggering another autosave. Logger.log("d", "Autosaving preferences, instances and profiles") diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index aba94e8c60..d7ab18b09e 100755 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -1,15 +1,21 @@ # Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. + +import numpy +import math + +from typing import List, Optional, TYPE_CHECKING, Any, Set, cast, Iterable, Dict + from UM.Mesh.MeshData import MeshData -from cura.Scene.CuraSceneNode import CuraSceneNode -from cura.Settings.ExtruderManager import ExtruderManager +from UM.Mesh.MeshBuilder import MeshBuilder + from UM.Application import Application #To modify the maximum zoom level. from UM.i18n import i18nCatalog from UM.Scene.Platform import Platform from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator from UM.Scene.SceneNode import SceneNode from UM.Resources import Resources -from UM.Mesh.MeshBuilder import MeshBuilder + from UM.Math.Vector import Vector from UM.Math.Matrix import Matrix from UM.Math.Color import Color @@ -17,23 +23,23 @@ from UM.Math.AxisAlignedBox import AxisAlignedBox from UM.Math.Polygon import Polygon from UM.Message import Message from UM.Signal import Signal -from PyQt5.QtCore import QTimer from UM.View.RenderBatch import RenderBatch from UM.View.GL.OpenGL import OpenGL + from cura.Settings.GlobalStack import GlobalStack +from cura.Scene.CuraSceneNode import CuraSceneNode +from cura.Settings.ExtruderManager import ExtruderManager -catalog = i18nCatalog("cura") +from PyQt5.QtCore import QTimer -import numpy -import math - -from typing import List, Optional, TYPE_CHECKING, Any, Set, cast, Iterable, Dict if TYPE_CHECKING: from cura.CuraApplication import CuraApplication from cura.Settings.ExtruderStack import ExtruderStack from UM.Settings.ContainerStack import ContainerStack +catalog = i18nCatalog("cura") + # Radius of disallowed area in mm around prime. I.e. how much distance to keep from prime position. PRIME_CLEARANCE = 6.5 @@ -1012,13 +1018,13 @@ class BuildVolume(SceneNode): all_values = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, "value") all_types = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, "type") for i, (setting_value, setting_type) in enumerate(zip(all_values, all_types)): - if not setting_value and (setting_type == "int" or setting_type == "float"): + if not setting_value and setting_type in ["int", "float"]: all_values[i] = 0 return all_values def _calculateBedAdhesionSize(self, used_extruders): if self._global_container_stack is None: - return + return None container_stack = self._global_container_stack adhesion_type = container_stack.getProperty("adhesion_type", "value") diff --git a/cura/CrashHandler.py b/cura/CrashHandler.py index 6b33dc2d03..e72180887c 100644 --- a/cura/CrashHandler.py +++ b/cura/CrashHandler.py @@ -58,6 +58,8 @@ class CrashHandler: self.traceback = tb self.has_started = has_started self.dialog = None # Don't create a QDialog before there is a QApplication + self.cura_version = None + self.cura_locale = None Logger.log("c", "An uncaught error has occurred!") for line in traceback.format_exception(exception_type, value, tb): diff --git a/cura/CuraActions.py b/cura/CuraActions.py index b92abbe706..20c44c7916 100644 --- a/cura/CuraActions.py +++ b/cura/CuraActions.py @@ -3,17 +3,15 @@ from PyQt5.QtCore import QObject, QUrl from PyQt5.QtGui import QDesktopServices -from typing import List, Optional, cast +from typing import List, cast from UM.Event import CallFunctionEvent from UM.FlameProfiler import pyqtSlot -from UM.Math.Quaternion import Quaternion from UM.Math.Vector import Vector from UM.Scene.Selection import Selection from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator from UM.Operations.GroupedOperation import GroupedOperation from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation -from UM.Operations.RotateOperation import RotateOperation from UM.Operations.TranslateOperation import TranslateOperation import cura.CuraApplication diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 8dd49c74f1..f778cb0fab 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -4,7 +4,7 @@ import os import sys import time -from typing import cast, TYPE_CHECKING, Optional, Callable, List +from typing import cast, TYPE_CHECKING, Optional, Callable, List, Any import numpy @@ -15,7 +15,7 @@ from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qm from UM.i18n import i18nCatalog from UM.Application import Application -from UM.Decorators import override, deprecated +from UM.Decorators import override from UM.FlameProfiler import pyqtSlot from UM.Logger import Logger from UM.Message import Message @@ -193,7 +193,7 @@ class CuraApplication(QtApplication): self._cura_package_manager = None - self._machine_action_manager = None + self._machine_action_manager = None # type: Optional[MachineActionManager.MachineActionManager] self.empty_container = None # type: EmptyInstanceContainer self.empty_definition_changes_container = None # type: EmptyInstanceContainer @@ -266,7 +266,6 @@ class CuraApplication(QtApplication): # Backups self._auto_save = None # type: Optional[AutoSave] - from cura.Settings.CuraContainerRegistry import CuraContainerRegistry self._container_registry_class = CuraContainerRegistry # Redefined here in order to please the typing. self._container_registry = None # type: CuraContainerRegistry @@ -699,7 +698,7 @@ class CuraApplication(QtApplication): self._message_box_callback_arguments = [] # Cura has multiple locations where instance containers need to be saved, so we need to handle this differently. - def saveSettings(self): + def saveSettings(self) -> None: if not self.started: # Do not do saving during application start or when data should not be saved on quit. return @@ -989,8 +988,8 @@ class CuraApplication(QtApplication): ## Get the machine action manager # We ignore any *args given to this, as we also register the machine manager as qml singleton. # It wants to give this function an engine and script engine, but we don't care about that. - def getMachineActionManager(self, *args): - return self._machine_action_manager + def getMachineActionManager(self, *args: Any) -> MachineActionManager.MachineActionManager: + return cast(MachineActionManager.MachineActionManager, self._machine_action_manager) @pyqtSlot(result = QObject) def getMaterialManagementModel(self) -> MaterialManagementModel: @@ -1443,7 +1442,7 @@ class CuraApplication(QtApplication): if center is not None: object_centers.append(center) - if object_centers and len(object_centers) > 0: + if object_centers: middle_x = sum([v.x for v in object_centers]) / len(object_centers) middle_y = sum([v.y for v in object_centers]) / len(object_centers) middle_z = sum([v.z for v in object_centers]) / len(object_centers) @@ -1493,7 +1492,7 @@ class CuraApplication(QtApplication): if center is not None: object_centers.append(center) - if object_centers and len(object_centers) > 0: + if object_centers: middle_x = sum([v.x for v in object_centers]) / len(object_centers) middle_y = sum([v.y for v in object_centers]) / len(object_centers) middle_z = sum([v.z for v in object_centers]) / len(object_centers) @@ -1675,7 +1674,7 @@ class CuraApplication(QtApplication): extension = os.path.splitext(f)[1] extension = extension.lower() filename = os.path.basename(f) - if len(self._currently_loading_files) > 0: + if self._currently_loading_files: # If a non-slicable file is already being loaded, we prevent loading of any further non-slicable files if extension in self._non_sliceable_extensions: message = Message( @@ -1796,8 +1795,8 @@ class CuraApplication(QtApplication): node.addDecorator(build_plate_decorator) build_plate_decorator.setBuildPlateNumber(target_build_plate) - op = AddSceneNodeOperation(node, scene.getRoot()) - op.push() + operation = AddSceneNodeOperation(node, scene.getRoot()) + operation.push() node.callDecoration("setActiveExtruder", default_extruder_id) scene.sceneChanged.emit(node) @@ -1871,16 +1870,14 @@ class CuraApplication(QtApplication): main_window = QtApplication.getInstance().getMainWindow() if main_window: return main_window.width() - else: - return 0 + return 0 @pyqtSlot(result = int) def appHeight(self) -> int: main_window = QtApplication.getInstance().getMainWindow() if main_window: return main_window.height() - else: - return 0 + return 0 @pyqtSlot() def deleteAll(self, only_selectable: bool = True) -> None: diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index 6422469bdf..a0d3a8d44a 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -1,7 +1,7 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import List, Tuple +from typing import List, Tuple, TYPE_CHECKING, Optional from cura.CuraApplication import CuraApplication #To find some resource types. from cura.Settings.GlobalStack import GlobalStack @@ -9,12 +9,16 @@ from cura.Settings.GlobalStack import GlobalStack from UM.PackageManager import PackageManager #The class we're extending. from UM.Resources import Resources #To find storage paths for some resource types. +if TYPE_CHECKING: + from UM.Qt.QtApplication import QtApplication + from PyQt5.QtCore import QObject + class CuraPackageManager(PackageManager): - def __init__(self, application, parent = None): + def __init__(self, application: "QtApplication", parent: Optional["QObject"] = None) -> None: super().__init__(application, parent) - def initialize(self): + def initialize(self) -> None: self._installation_dirs_dict["materials"] = Resources.getStoragePath(CuraApplication.ResourceTypes.MaterialInstanceContainer) self._installation_dirs_dict["qualities"] = Resources.getStoragePath(CuraApplication.ResourceTypes.QualityInstanceContainer) diff --git a/cura/CuraView.py b/cura/CuraView.py index b358558dff..d594ea9571 100644 --- a/cura/CuraView.py +++ b/cura/CuraView.py @@ -26,6 +26,7 @@ class CuraView(View): def mainComponent(self) -> QUrl: return self.getDisplayComponent("main") + @pyqtProperty(QUrl, constant = True) def stageMenuComponent(self) -> QUrl: url = self.getDisplayComponent("menu") diff --git a/cura/Layer.py b/cura/Layer.py index 73fda64a45..933d4436c9 100644 --- a/cura/Layer.py +++ b/cura/Layer.py @@ -33,10 +33,10 @@ class Layer: def elementCount(self): return self._element_count - def setHeight(self, height): + def setHeight(self, height: float) -> None: self._height = height - def setThickness(self, thickness): + def setThickness(self, thickness: float) -> None: self._thickness = thickness def lineMeshVertexCount(self) -> int: diff --git a/cura/LayerData.py b/cura/LayerData.py index 796e71cbdc..72824591ab 100644 --- a/cura/LayerData.py +++ b/cura/LayerData.py @@ -16,8 +16,7 @@ class LayerData(MeshData): def getLayer(self, layer): if layer in self._layers: return self._layers[layer] - else: - return None + return None def getLayers(self): return self._layers diff --git a/cura/LayerDataDecorator.py b/cura/LayerDataDecorator.py index ef82d8f5cc..36466cac72 100644 --- a/cura/LayerDataDecorator.py +++ b/cura/LayerDataDecorator.py @@ -9,7 +9,7 @@ from cura.LayerData import LayerData ## Simple decorator to indicate a scene node holds layer data. class LayerDataDecorator(SceneNodeDecorator): - def __init__(self): + def __init__(self) -> None: super().__init__() self._layer_data = None # type: Optional[LayerData] diff --git a/cura/LayerPolygon.py b/cura/LayerPolygon.py index 0d6489aaa2..70d818f1ca 100644 --- a/cura/LayerPolygon.py +++ b/cura/LayerPolygon.py @@ -1,10 +1,11 @@ # Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. - -from UM.Qt.QtApplication import QtApplication -from typing import Any, Optional import numpy +from typing import Optional, cast + +from UM.Qt.Bindings.Theme import Theme +from UM.Qt.QtApplication import QtApplication from UM.Logger import Logger @@ -61,7 +62,7 @@ class LayerPolygon: # When type is used as index returns true if type == LayerPolygon.InfillType or type == LayerPolygon.SkinType or type == LayerPolygon.SupportInfillType # Should be generated in better way, not hardcoded. - self._isInfillOrSkinTypeMap = numpy.array([0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0], dtype = numpy.bool) + self._is_infill_or_skin_type_map = numpy.array([0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0], dtype = numpy.bool) self._build_cache_line_mesh_mask = None # type: Optional[numpy.ndarray] self._build_cache_needed_points = None # type: Optional[numpy.ndarray] @@ -149,17 +150,17 @@ class LayerPolygon: def getColors(self): return self._colors - def mapLineTypeToColor(self, line_types): + def mapLineTypeToColor(self, line_types: numpy.ndarray) -> numpy.ndarray: return self._color_map[line_types] - def isInfillOrSkinType(self, line_types): - return self._isInfillOrSkinTypeMap[line_types] + def isInfillOrSkinType(self, line_types: numpy.ndarray) -> numpy.ndarray: + return self._is_infill_or_skin_type_map[line_types] - def lineMeshVertexCount(self): - return (self._vertex_end - self._vertex_begin) + def lineMeshVertexCount(self) -> int: + return self._vertex_end - self._vertex_begin - def lineMeshElementCount(self): - return (self._index_end - self._index_begin) + def lineMeshElementCount(self) -> int: + return self._index_end - self._index_begin @property def extruder(self): @@ -202,7 +203,7 @@ class LayerPolygon: return self._jump_count # Calculate normals for the entire polygon using numpy. - def getNormals(self): + def getNormals(self) -> numpy.ndarray: normals = numpy.copy(self._data) normals[:, 1] = 0.0 # We are only interested in 2D normals @@ -226,13 +227,13 @@ class LayerPolygon: return normals - __color_map = None # type: numpy.ndarray[Any] + __color_map = None # type: numpy.ndarray ## Gets the instance of the VersionUpgradeManager, or creates one. @classmethod - def getColorMap(cls): + def getColorMap(cls) -> numpy.ndarray: if cls.__color_map is None: - theme = QtApplication.getInstance().getTheme() + theme = cast(Theme, QtApplication.getInstance().getTheme()) cls.__color_map = numpy.array([ theme.getColor("layerview_none").getRgbF(), # NoneType theme.getColor("layerview_inset_0").getRgbF(), # Inset0Type diff --git a/cura/Machines/ContainerNode.py b/cura/Machines/ContainerNode.py index a8bbf0a537..8a9ddcc39b 100644 --- a/cura/Machines/ContainerNode.py +++ b/cura/Machines/ContainerNode.py @@ -26,7 +26,7 @@ class ContainerNode: ## Gets the metadata of the container that this node represents. # Getting the metadata from the container directly is about 10x as fast. # \return The metadata of the container in this node. - def getMetadata(self): + def getMetadata(self) -> Dict[str, Any]: return ContainerRegistry.getInstance().findContainersMetadata(id = self.container_id)[0] ## Get an entry from the metadata of the container that this node contains. diff --git a/cura/Machines/ContainerTree.py b/cura/Machines/ContainerTree.py index c2bfabea2c..a7bb0610bd 100644 --- a/cura/Machines/ContainerTree.py +++ b/cura/Machines/ContainerTree.py @@ -30,7 +30,7 @@ if TYPE_CHECKING: # nodes that have children) but that child node may be a node representing the # empty instance container. class ContainerTree: - __instance = None + __instance = None # type: Optional["ContainerTree"] @classmethod def getInstance(cls): @@ -75,7 +75,7 @@ class ContainerTree: return self.machines[global_stack.definition.getId()].getQualityChangesGroups(variant_names, material_bases, extruder_enabled) ## Ran after completely starting up the application. - def _onStartupFinished(self): + def _onStartupFinished(self) -> None: currently_added = ContainerRegistry.getInstance().findContainerStacks() # Find all currently added global stacks. JobQueue.getInstance().add(self._MachineNodeLoadJob(self, currently_added)) @@ -137,7 +137,7 @@ class ContainerTree: # \param container_stacks All of the stacks to pre-load the container # trees for. This needs to be provided from here because the stacks # need to be constructed on the main thread because they are QObject. - def __init__(self, tree_root: "ContainerTree", container_stacks: List["ContainerStack"]): + def __init__(self, tree_root: "ContainerTree", container_stacks: List["ContainerStack"]) -> None: self.tree_root = tree_root self.container_stacks = container_stacks super().__init__() diff --git a/cura/Machines/MachineErrorChecker.py b/cura/Machines/MachineErrorChecker.py index 4c6ed891b1..7a5291dac5 100644 --- a/cura/Machines/MachineErrorChecker.py +++ b/cura/Machines/MachineErrorChecker.py @@ -6,13 +6,13 @@ import time from collections import deque from PyQt5.QtCore import QObject, QTimer, pyqtSignal, pyqtProperty +from typing import Optional, Any, Set -from UM.Application import Application from UM.Logger import Logger from UM.Settings.SettingDefinition import SettingDefinition from UM.Settings.Validator import ValidatorState - +import cura.CuraApplication # # This class performs setting error checks for the currently active machine. # @@ -24,25 +24,25 @@ from UM.Settings.Validator import ValidatorState # class MachineErrorChecker(QObject): - def __init__(self, parent = None): + def __init__(self, parent: Optional[QObject] = None) -> None: super().__init__(parent) self._global_stack = None self._has_errors = True # Result of the error check, indicating whether there are errors in the stack - self._error_keys = set() # A set of settings keys that have errors - self._error_keys_in_progress = set() # The variable that stores the results of the currently in progress check + self._error_keys = set() # type: Set[str] # A set of settings keys that have errors + self._error_keys_in_progress = set() # type: Set[str] # The variable that stores the results of the currently in progress check - self._stacks_and_keys_to_check = None # a FIFO queue of tuples (stack, key) to check for errors + self._stacks_and_keys_to_check = None # type: Optional[deque] # a FIFO queue of tuples (stack, key) to check for errors self._need_to_check = False # Whether we need to schedule a new check or not. This flag is set when a new # error check needs to take place while there is already one running at the moment. self._check_in_progress = False # Whether there is an error check running in progress at the moment. - self._application = Application.getInstance() + self._application = cura.CuraApplication.CuraApplication.getInstance() self._machine_manager = self._application.getMachineManager() - self._start_time = 0 # measure checking time + self._start_time = 0. # measure checking time # This timer delays the starting of error check so we can react less frequently if the user is frequently # changing settings. @@ -94,13 +94,13 @@ class MachineErrorChecker(QObject): # Start the error check for property changed # this is seperate from the startErrorCheck because it ignores a number property types - def startErrorCheckPropertyChanged(self, key, property_name): + def startErrorCheckPropertyChanged(self, key: str, property_name: str) -> None: if property_name != "value": return self.startErrorCheck() # Starts the error check timer to schedule a new error check. - def startErrorCheck(self, *args) -> None: + def startErrorCheck(self, *args: Any) -> None: if not self._check_in_progress: self._need_to_check = True self.needToWaitForResultChanged.emit() diff --git a/cura/Machines/MachineNode.py b/cura/Machines/MachineNode.py index cee71160d2..0974c3dca7 100644 --- a/cura/Machines/MachineNode.py +++ b/cura/Machines/MachineNode.py @@ -176,9 +176,9 @@ class MachineNode(ContainerNode): # Find the global qualities for this printer. global_qualities = container_registry.findInstanceContainersMetadata(type = "quality", definition = self.quality_definition, global_quality = "True") # First try specific to this printer. - if len(global_qualities) == 0: # This printer doesn't override the global qualities. + if not global_qualities: # This printer doesn't override the global qualities. global_qualities = container_registry.findInstanceContainersMetadata(type = "quality", definition = "fdmprinter", global_quality = "True") # Otherwise pick the global global qualities. - if len(global_qualities) == 0: # There are no global qualities either?! Something went very wrong, but we'll not crash and properly fill the tree. + if not global_qualities: # There are no global qualities either?! Something went very wrong, but we'll not crash and properly fill the tree. global_qualities = [cura.CuraApplication.CuraApplication.getInstance().empty_quality_container.getMetaData()] for global_quality in global_qualities: self.global_qualities[global_quality["quality_type"]] = QualityNode(global_quality["id"], parent = self) diff --git a/cura/Machines/MaterialNode.py b/cura/Machines/MaterialNode.py index 5bcaf12bfb..dcd4adcfdb 100644 --- a/cura/Machines/MaterialNode.py +++ b/cura/Machines/MaterialNode.py @@ -14,6 +14,7 @@ if TYPE_CHECKING: from typing import Dict from cura.Machines.VariantNode import VariantNode + ## Represents a material in the container tree. # # Its subcontainers are quality profiles. diff --git a/cura/Machines/Models/MaterialBrandsModel.py b/cura/Machines/Models/MaterialBrandsModel.py index 184d27f390..b0594cb286 100644 --- a/cura/Machines/Models/MaterialBrandsModel.py +++ b/cura/Machines/Models/MaterialBrandsModel.py @@ -34,7 +34,7 @@ class MaterialBrandsModel(BaseMaterialsModel): brand_item_list = [] brand_group_dict = {} - # Part 1: Generate the entire tree of brands -> material types -> spcific materials + # Part 1: Generate the entire tree of brands -> material types -> specific materials for root_material_id, container_node in self._available_materials.items(): # Do not include the materials from a to-be-removed package if bool(container_node.getMetaDataEntry("removed", False)): diff --git a/cura/Machines/QualityNode.py b/cura/Machines/QualityNode.py index 45cd898db5..7696dfb117 100644 --- a/cura/Machines/QualityNode.py +++ b/cura/Machines/QualityNode.py @@ -41,4 +41,4 @@ class QualityNode(ContainerNode): self.intents[intent["id"]] = IntentNode(intent["id"], quality = self) self.intents["empty_intent"] = IntentNode("empty_intent", quality = self) - # Otherwise, there are no intents for global profiles. \ No newline at end of file + # Otherwise, there are no intents for global profiles. diff --git a/cura/Machines/VariantNode.py b/cura/Machines/VariantNode.py index 550b5881a3..0f30782a91 100644 --- a/cura/Machines/VariantNode.py +++ b/cura/Machines/VariantNode.py @@ -51,7 +51,7 @@ class VariantNode(ContainerNode): # Find all the materials for this variant's name. else: # Printer has its own material profiles. Look for material profiles with this printer's definition. base_materials = container_registry.findInstanceContainersMetadata(type = "material", definition = "fdmprinter") - printer_specific_materials = container_registry.findInstanceContainersMetadata(type = "material", definition = self.machine.container_id, variant_name = None) + printer_specific_materials = container_registry.findInstanceContainersMetadata(type = "material", definition = self.machine.container_id) variant_specific_materials = container_registry.findInstanceContainersMetadata(type = "material", definition = self.machine.container_id, variant_name = self.variant_name) # If empty_variant, this won't return anything. materials_per_base_file = {material["base_file"]: material for material in base_materials} materials_per_base_file.update({material["base_file"]: material for material in printer_specific_materials}) # Printer-specific profiles override global ones. diff --git a/cura/MultiplyObjectsJob.py b/cura/MultiplyObjectsJob.py index 5c25f70336..134e579746 100644 --- a/cura/MultiplyObjectsJob.py +++ b/cura/MultiplyObjectsJob.py @@ -47,7 +47,7 @@ class MultiplyObjectsJob(Job): nodes = [] not_fit_count = 0 - + found_solution_for_all = False for node in self._objects: # If object is part of a group, multiply group current_node = node @@ -66,7 +66,7 @@ class MultiplyObjectsJob(Job): found_solution_for_all = True arranger.resetLastPriority() - for i in range(self._count): + for _ in range(self._count): # We do place the nodes one by one, as we want to yield in between. new_node = copy.deepcopy(node) solution_found = False @@ -98,10 +98,10 @@ class MultiplyObjectsJob(Job): Job.yieldThread() if nodes: - op = GroupedOperation() + operation = GroupedOperation() for new_node in nodes: - op.addOperation(AddSceneNodeOperation(new_node, current_node.getParent())) - op.push() + operation.addOperation(AddSceneNodeOperation(new_node, current_node.getParent())) + operation.push() status_message.hide() if not found_solution_for_all: diff --git a/cura/OAuth2/Models.py b/cura/OAuth2/Models.py index 468351c62b..dd935fef6e 100644 --- a/cura/OAuth2/Models.py +++ b/cura/OAuth2/Models.py @@ -1,10 +1,10 @@ # Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import Optional +from typing import Optional, Dict, Any class BaseModel: - def __init__(self, **kwargs): + def __init__(self, **kwargs: Any) -> None: self.__dict__.update(kwargs) @@ -53,9 +53,10 @@ class ResponseData(BaseModel): redirect_uri = None # type: Optional[str] content_type = "text/html" # type: str + ## Possible HTTP responses. HTTP_STATUS = { "OK": ResponseStatus(code = 200, message = "OK"), "NOT_FOUND": ResponseStatus(code = 404, message = "NOT FOUND"), "REDIRECT": ResponseStatus(code = 302, message = "REDIRECT") -} +} # type: Dict[str, ResponseStatus] diff --git a/cura/OneAtATimeIterator.py b/cura/OneAtATimeIterator.py index b77e1f3982..3373f2104f 100644 --- a/cura/OneAtATimeIterator.py +++ b/cura/OneAtATimeIterator.py @@ -122,6 +122,6 @@ class _ObjectOrder: # \param order List of indices in which to print objects, ordered by printing # order. # \param todo: List of indices which are not yet inserted into the order list. - def __init__(self, order: List[SceneNode], todo: List[SceneNode]): + def __init__(self, order: List[SceneNode], todo: List[SceneNode]) -> None: self.order = order self.todo = todo diff --git a/cura/Operations/PlatformPhysicsOperation.py b/cura/Operations/PlatformPhysicsOperation.py index 9571679c3c..0d69320eec 100644 --- a/cura/Operations/PlatformPhysicsOperation.py +++ b/cura/Operations/PlatformPhysicsOperation.py @@ -1,26 +1,27 @@ # Copyright (c) 2015 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. - +from UM.Math.Vector import Vector from UM.Operations.Operation import Operation from UM.Operations.GroupedOperation import GroupedOperation from UM.Scene.SceneNode import SceneNode + ## A specialised operation designed specifically to modify the previous operation. class PlatformPhysicsOperation(Operation): - def __init__(self, node, translation): + def __init__(self, node: SceneNode, translation: Vector) -> None: super().__init__() self._node = node self._old_transformation = node.getLocalTransformation() self._translation = translation self._always_merge = True - def undo(self): + def undo(self) -> None: self._node.setTransformation(self._old_transformation) - def redo(self): + def redo(self) -> None: self._node.translate(self._translation, SceneNode.TransformSpace.World) - def mergeWith(self, other): + def mergeWith(self, other: Operation) -> GroupedOperation: group = GroupedOperation() group.addOperation(other) @@ -28,5 +29,5 @@ class PlatformPhysicsOperation(Operation): return group - def __repr__(self): + def __repr__(self) -> str: return "PlatformPhysicsOp.(trans.={0})".format(self._translation) diff --git a/cura/Operations/SetBuildPlateNumberOperation.py b/cura/Operations/SetBuildPlateNumberOperation.py index 96230639f9..fd48cf47d9 100644 --- a/cura/Operations/SetBuildPlateNumberOperation.py +++ b/cura/Operations/SetBuildPlateNumberOperation.py @@ -6,9 +6,9 @@ from UM.Operations.Operation import Operation from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator + ## Simple operation to set the buildplate number of a scenenode. class SetBuildPlateNumberOperation(Operation): - def __init__(self, node: SceneNode, build_plate_nr: int) -> None: super().__init__() self._node = node @@ -16,11 +16,11 @@ class SetBuildPlateNumberOperation(Operation): self._previous_build_plate_nr = None self._decorator_added = False - def undo(self): + def undo(self) -> None: if self._previous_build_plate_nr: self._node.callDecoration("setBuildPlateNumber", self._previous_build_plate_nr) - def redo(self): + def redo(self) -> None: stack = self._node.callDecoration("getStack") #Don't try to get the active extruder since it may be None anyway. if not stack: self._node.addDecorator(SettingOverrideDecorator()) diff --git a/cura/Operations/SetParentOperation.py b/cura/Operations/SetParentOperation.py index 612c02f18d..7d71572a93 100644 --- a/cura/Operations/SetParentOperation.py +++ b/cura/Operations/SetParentOperation.py @@ -1,36 +1,37 @@ # Copyright (c) 2016 Ultimaker B.V. # Uranium is released under the terms of the LGPLv3 or higher. +from typing import Optional from UM.Scene.SceneNode import SceneNode from UM.Operations import Operation from UM.Math.Vector import Vector -## An operation that parents a scene node to another scene node. +## An operation that parents a scene node to another scene node. class SetParentOperation(Operation.Operation): ## Initialises this SetParentOperation. # # \param node The node which will be reparented. # \param parent_node The node which will be the parent. - def __init__(self, node, parent_node): + def __init__(self, node: SceneNode, parent_node: Optional[SceneNode]) -> None: super().__init__() self._node = node self._parent = parent_node self._old_parent = node.getParent() # To restore the previous parent in case of an undo. ## Undoes the set-parent operation, restoring the old parent. - def undo(self): + def undo(self) -> None: self._set_parent(self._old_parent) ## Re-applies the set-parent operation. - def redo(self): + def redo(self) -> None: self._set_parent(self._parent) ## Sets the parent of the node while applying transformations to the world-transform of the node stays the same. # # \param new_parent The new parent. Note: this argument can be None, which would hide the node from the scene. - def _set_parent(self, new_parent): + def _set_parent(self, new_parent: Optional[SceneNode]) -> None: if new_parent: current_parent = self._node.getParent() if current_parent: @@ -59,5 +60,5 @@ class SetParentOperation(Operation.Operation): ## Returns a programmer-readable representation of this operation. # # \return A programmer-readable representation of this operation. - def __repr__(self): + def __repr__(self) -> str: return "SetParentOperation(node = {0}, parent_node={1})".format(self._node, self._parent) diff --git a/cura/PreviewPass.py b/cura/PreviewPass.py index 58205ba708..da60db2d99 100644 --- a/cura/PreviewPass.py +++ b/cura/PreviewPass.py @@ -17,9 +17,6 @@ from cura.Scene.CuraSceneNode import CuraSceneNode if TYPE_CHECKING: from UM.View.GL.ShaderProgram import ShaderProgram - -MYPY = False -if MYPY: from UM.Scene.Camera import Camera diff --git a/cura/PrintJobPreviewImageProvider.py b/cura/PrintJobPreviewImageProvider.py index a8df5aa273..8b46c6db37 100644 --- a/cura/PrintJobPreviewImageProvider.py +++ b/cura/PrintJobPreviewImageProvider.py @@ -3,6 +3,7 @@ from PyQt5.QtQuick import QQuickImageProvider from PyQt5.QtCore import QSize from UM.Application import Application +from typing import Tuple class PrintJobPreviewImageProvider(QQuickImageProvider): @@ -10,7 +11,7 @@ class PrintJobPreviewImageProvider(QQuickImageProvider): super().__init__(QQuickImageProvider.Image) ## Request a new image. - def requestImage(self, id: str, size: QSize) -> QImage: + def requestImage(self, id: str, size: QSize) -> Tuple[QImage, QSize]: # The id will have an uuid and an increment separated by a slash. As we don't care about the value of the # increment, we need to strip that first. uuid = id[id.find("/") + 1:] @@ -22,6 +23,6 @@ class PrintJobPreviewImageProvider(QQuickImageProvider): if print_job.key == uuid: if print_job.getPreviewImage(): return print_job.getPreviewImage(), QSize(15, 15) - else: - return QImage(), QSize(15, 15) - return QImage(), QSize(15,15) \ No newline at end of file + + return QImage(), QSize(15, 15) + return QImage(), QSize(15, 15) \ No newline at end of file diff --git a/cura/PrinterOutput/Models/PrintJobOutputModel.py b/cura/PrinterOutput/Models/PrintJobOutputModel.py index b4296a5494..256999b96f 100644 --- a/cura/PrinterOutput/Models/PrintJobOutputModel.py +++ b/cura/PrinterOutput/Models/PrintJobOutputModel.py @@ -161,7 +161,7 @@ class PrintJobOutputModel(QObject): self._time_elapsed = new_time_elapsed self.timeElapsedChanged.emit() - def updateState(self, new_state): + def updateState(self, new_state: str) -> None: if self._state != new_state: self._state = new_state self.stateChanged.emit() diff --git a/cura/PrinterOutput/PrinterOutputDevice.py b/cura/PrinterOutput/PrinterOutputDevice.py index b05e76ad2e..0e0ad488b1 100644 --- a/cura/PrinterOutput/PrinterOutputDevice.py +++ b/cura/PrinterOutput/PrinterOutputDevice.py @@ -148,7 +148,7 @@ class PrinterOutputDevice(QObject, OutputDevice): @pyqtProperty(QObject, notify = printersChanged) def activePrinter(self) -> Optional["PrinterOutputModel"]: - if len(self._printers): + if self._printers: return self._printers[0] return None diff --git a/cura/Scene/BlockSlicingDecorator.py b/cura/Scene/BlockSlicingDecorator.py index 0536e1635f..3f0d57a83f 100644 --- a/cura/Scene/BlockSlicingDecorator.py +++ b/cura/Scene/BlockSlicingDecorator.py @@ -10,3 +10,6 @@ class BlockSlicingDecorator(SceneNodeDecorator): def isBlockSlicing(self) -> bool: return True + + def __deepcopy__(self, memo): + return BlockSlicingDecorator() \ No newline at end of file diff --git a/cura/Scene/GCodeListDecorator.py b/cura/Scene/GCodeListDecorator.py index 6c52fb89bf..b8db706db3 100644 --- a/cura/Scene/GCodeListDecorator.py +++ b/cura/Scene/GCodeListDecorator.py @@ -17,8 +17,8 @@ class GCodeListDecorator(SceneNodeDecorator): def getGCodeList(self) -> List[str]: return self._gcode_list - def setGCodeList(self, list: List[str]) -> None: - self._gcode_list = list + def setGCodeList(self, gcode_list: List[str]) -> None: + self._gcode_list = gcode_list def __deepcopy__(self, memo) -> "GCodeListDecorator": copied_decorator = GCodeListDecorator() diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index f6028e9d4d..0ef09a1fac 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -15,7 +15,6 @@ from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerStack import ContainerStack from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.SettingInstance import SettingInstance -from UM.Application import Application from UM.Logger import Logger from UM.Message import Message from UM.Platform import Platform @@ -176,7 +175,7 @@ class CuraContainerRegistry(ContainerRegistry): if not file_name: return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags !", "Failed to import profile from {0}: {1}", file_name, "Invalid path")} - global_stack = Application.getInstance().getGlobalContainerStack() + global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() if not global_stack: return {"status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags !", "Can't import profile from {0} before a printer is added.", file_name)} container_tree = ContainerTree.getInstance() @@ -384,7 +383,7 @@ class CuraContainerRegistry(ContainerRegistry): if not quality_type: return catalog.i18nc("@info:status", "Profile is missing a quality type.") - global_stack = Application.getInstance().getGlobalContainerStack() + global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() if global_stack is None: return None definition_id = ContainerTree.getInstance().machines[global_stack.definition.getId()].quality_definition diff --git a/cura/UI/MachineActionManager.py b/cura/UI/MachineActionManager.py index aa90e909e2..6efd3217a1 100644 --- a/cura/UI/MachineActionManager.py +++ b/cura/UI/MachineActionManager.py @@ -43,7 +43,7 @@ class MachineActionManager(QObject): # Dict of all actions that need to be done when first added by definition ID self._first_start_actions = {} # type: Dict[str, List[MachineAction]] - def initialize(self): + def initialize(self) -> None: # Add machine_action as plugin type PluginRegistry.addType("machine_action", self.addMachineAction) diff --git a/plugins/PostProcessingPlugin/scripts/Stretch.py b/plugins/PostProcessingPlugin/scripts/Stretch.py index 20eef60ef2..3899bd2c50 100644 --- a/plugins/PostProcessingPlugin/scripts/Stretch.py +++ b/plugins/PostProcessingPlugin/scripts/Stretch.py @@ -35,7 +35,7 @@ class GCodeStep(): Class to store the current value of each G_Code parameter for any G-Code step """ - def __init__(self, step, in_relative_movement: bool = False): + def __init__(self, step, in_relative_movement: bool = False) -> None: self.step = step self.step_x = 0 self.step_y = 0 diff --git a/plugins/SimulationView/SimulationViewProxy.py b/plugins/SimulationView/SimulationViewProxy.py index 1183244ab3..ce2c336257 100644 --- a/plugins/SimulationView/SimulationViewProxy.py +++ b/plugins/SimulationView/SimulationViewProxy.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: class SimulationViewProxy(QObject): - def __init__(self, simulation_view: "SimulationView", parent=None): + def __init__(self, simulation_view: "SimulationView", parent=None) -> None: super().__init__(parent) self._simulation_view = simulation_view self._current_layer = 0 diff --git a/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml b/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml index 38cd2b3a42..9a439930d3 100644 --- a/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml +++ b/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml @@ -74,7 +74,7 @@ UM.Dialog{ } Label { - text: model.name + text: model.display_name font: UM.Theme.getFont("medium_bold") anchors.left: packageIcon.right anchors.leftMargin: UM.Theme.getSize("default_margin").width @@ -104,7 +104,7 @@ UM.Dialog{ { width: parent.width property int lineHeight: 60 - visible: !model.is_compatible + visible: !model.is_compatible && !model.is_dismissed height: visible ? (lineHeight + UM.Theme.getSize("default_margin").height) : 0 // We only show the incompatible packages here Image { @@ -117,7 +117,7 @@ UM.Dialog{ } Label { - text: model.name + text: model.display_name font: UM.Theme.getFont("medium_bold") anchors.left: packageIcon.right anchors.leftMargin: UM.Theme.getSize("default_margin").width @@ -125,6 +125,26 @@ UM.Dialog{ color: UM.Theme.getColor("text") elide: Text.ElideRight } + UM.TooltipArea + { + width: childrenRect.width; + height: childrenRect.height; + text: catalog.i18nc("@info:tooltip", "Dismisses the package and won't be shown in this dialog anymore") + anchors.right: parent.right + anchors.verticalCenter: packageIcon.verticalCenter + Label + { + text: "(Dismiss)" + font: UM.Theme.getFont("small") + color: UM.Theme.getColor("text") + MouseArea + { + cursorShape: Qt.PointingHandCursor + anchors.fill: parent + onClicked: toolbox.dismissIncompatiblePackage(model.package_id) + } + } + } } } } diff --git a/plugins/Toolbox/src/CloudSync/SubscribedPackagesModel.py b/plugins/Toolbox/src/CloudSync/SubscribedPackagesModel.py index cea4936655..4a0f559748 100644 --- a/plugins/Toolbox/src/CloudSync/SubscribedPackagesModel.py +++ b/plugins/Toolbox/src/CloudSync/SubscribedPackagesModel.py @@ -1,9 +1,11 @@ # Copyright (c) 2020 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from PyQt5.QtCore import Qt, pyqtProperty +from PyQt5.QtCore import Qt, pyqtProperty, pyqtSlot from UM.Qt.ListModel import ListModel from cura import ApplicationMetadata +from UM.Logger import Logger +from typing import List, Dict, Any class SubscribedPackagesModel(ListModel): @@ -15,9 +17,11 @@ class SubscribedPackagesModel(ListModel): self._discrepancies = None self._sdk_version = ApplicationMetadata.CuraSDKVersion - self.addRoleName(Qt.UserRole + 1, "name") - self.addRoleName(Qt.UserRole + 2, "icon_url") - self.addRoleName(Qt.UserRole + 3, "is_compatible") + self.addRoleName(Qt.UserRole + 1, "package_id") + self.addRoleName(Qt.UserRole + 2, "display_name") + self.addRoleName(Qt.UserRole + 3, "icon_url") + self.addRoleName(Qt.UserRole + 4, "is_compatible") + self.addRoleName(Qt.UserRole + 5, "is_dismissed") @pyqtProperty(bool, constant=True) def hasCompatiblePackages(self) -> bool: @@ -33,29 +37,36 @@ class SubscribedPackagesModel(ListModel): return True return False - def setMetadata(self, data): - if self._metadata != data: - self._metadata = data + # Sets the "is_compatible" to True for the given package, in memory - def addValue(self, discrepancy): - if self._discrepancies != discrepancy: - self._discrepancies = discrepancy + @pyqtSlot() + def dismissPackage(self, package_id: str) -> None: + package = self.find(key="package_id", value=package_id) + if package != -1: + self.setProperty(package, property="is_dismissed", value=True) + Logger.debug("Package {} has been dismissed".format(package_id)) + + def setMetadata(self, data: List[Dict[str, List[Any]]]) -> None: + self._metadata = data + + def addDiscrepancies(self, discrepancy: List[str]) -> None: + self._discrepancies = discrepancy def getCompatiblePackages(self): return [x for x in self._items if x["is_compatible"]] - def update(self): + def initialize(self) -> None: self._items.clear() - for item in self._metadata: if item["package_id"] not in self._discrepancies: continue package = { "package_id": item["package_id"], - "name": item["display_name"], + "display_name": item["display_name"], "sdk_versions": item["sdk_versions"], "download_url": item["download_url"], "md5_hash": item["md5_hash"], + "is_dismissed": False, } if self._sdk_version not in item["sdk_versions"]: package.update({"is_compatible": False}) @@ -65,7 +76,6 @@ class SubscribedPackagesModel(ListModel): package.update({"icon_url": item["icon_url"]}) except KeyError: # There is no 'icon_url" in the response payload for this package package.update({"icon_url": ""}) - self._items.append(package) self.setItems(self._items) diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index b1928b4e84..8004cca9ed 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -528,6 +528,11 @@ class Toolbox(QObject, Extension): populated += 1 return populated == len(self._server_response_data.items()) + @pyqtSlot(str) + def dismissIncompatiblePackage(self, package_id: str): + self._models["subscribed_packages"].dismissPackage(package_id) # sets "is_compatible" to True, in-memory + self._package_manager.dismissPackage(package_id) # adds this package_id as dismissed in the user config file + # Make API Calls # -------------------------------------------------------------------------- def _makeRequestByType(self, request_type: str) -> None: diff --git a/plugins/UM3NetworkPrinting/src/Models/Http/ClusterPrinterMaterialStationSlot.py b/plugins/UM3NetworkPrinting/src/Models/Http/ClusterPrinterMaterialStationSlot.py index 80deb1c9a8..b9c40592e5 100644 --- a/plugins/UM3NetworkPrinting/src/Models/Http/ClusterPrinterMaterialStationSlot.py +++ b/plugins/UM3NetworkPrinting/src/Models/Http/ClusterPrinterMaterialStationSlot.py @@ -14,7 +14,7 @@ class ClusterPrinterMaterialStationSlot(ClusterPrintCoreConfiguration): # \param material_remaining: How much material is remaining on the spool (between 0 and 1, or -1 for missing data). # \param material_empty: Whether the material spool is too empty to be used. def __init__(self, slot_index: int, compatible: bool, material_remaining: float, - material_empty: Optional[bool] = False, **kwargs): + material_empty: Optional[bool] = False, **kwargs) -> None: self.slot_index = slot_index self.compatible = compatible self.material_remaining = material_remaining diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index 8dcf60018f..d6f50f939b 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -1,4 +1,4 @@ -// Copyright (c) 2019 Ultimaker B.V. +// Copyright (c) 2020 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.7 @@ -21,7 +21,16 @@ UM.MainWindow id: base // Cura application window title - title: PrintInformation.jobName + " - " + catalog.i18nc("@title:window", CuraApplication.applicationDisplayName) + title: + { + let result = ""; + if(PrintInformation.jobName != "") + { + result += PrintInformation.jobName + " - "; + } + result += CuraApplication.applicationDisplayName; + return result; + } backgroundColor: UM.Theme.getColor("viewport_background") @@ -244,23 +253,6 @@ UM.MainWindow } } - Toolbar - { - // The toolbar is the left bar that is populated by all the tools (which are dynamicly populated by - // plugins) - id: toolbar - - property int mouseX: base.mouseX - property int mouseY: base.mouseY - - anchors - { - verticalCenter: parent.verticalCenter - left: parent.left - } - visible: CuraApplication.platformActivity && !PrintInformation.preSliced - } - ObjectSelector { id: objectSelector @@ -302,6 +294,23 @@ UM.MainWindow } } + Toolbar + { + // The toolbar is the left bar that is populated by all the tools (which are dynamicly populated by + // plugins) + id: toolbar + + property int mouseX: base.mouseX + property int mouseY: base.mouseY + + anchors + { + verticalCenter: parent.verticalCenter + left: parent.left + } + visible: CuraApplication.platformActivity && !PrintInformation.preSliced + } + // A hint for the loaded content view. Overlay items / controls can safely be placed in this area Item { id: mainSafeArea diff --git a/test-in-docker.sh b/test-in-docker.sh new file mode 100755 index 0000000000..e5a1116646 --- /dev/null +++ b/test-in-docker.sh @@ -0,0 +1,5 @@ +sudo rm -rf ./build ./Uranium +sudo docker run -it --rm \ + -v "$(pwd):/srv/cura" ultimaker/cura-build-environment \ + /srv/cura/docker/build.sh +sudo rm -rf ./build ./Uranium