Merge remote-tracking branch 'origin/master'

This commit is contained in:
Nino van Hooff 2020-01-13 14:43:24 +01:00
commit 4518132ffc
40 changed files with 312 additions and 139 deletions

116
.pylintrc Normal file
View File

@ -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}

View File

@ -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:
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

View File

@ -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

View File

@ -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")

View File

@ -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")

View File

@ -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):

View File

@ -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

View File

@ -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:

View File

@ -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):
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)

View File

@ -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")

View File

@ -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:

View File

@ -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

View File

@ -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]

View File

@ -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

View File

@ -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.

View File

@ -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__()

View File

@ -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()

View File

@ -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)

View File

@ -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.

View File

@ -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.
# Otherwise, there are no intents for global profiles.

View File

@ -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:

View File

@ -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]

View File

@ -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):
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)

View File

@ -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())

View File

@ -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]):
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)

View File

@ -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

View File

@ -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)
return QImage(), QSize(15, 15)
return QImage(), QSize(15, 15)

View File

@ -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()

View File

@ -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

View File

@ -10,3 +10,6 @@ class BlockSlicingDecorator(SceneNodeDecorator):
def isBlockSlicing(self) -> bool:
return True
def __deepcopy__(self, memo):
return BlockSlicingDecorator()

View File

@ -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()

View File

@ -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 <filename>!", "Failed to import profile from <filename>{0}</filename>: {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 <filename>!", "Can't import profile from <filename>{0}</filename> 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

View File

@ -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)

View File

@ -48,6 +48,7 @@ UM.Dialog{
{
font: UM.Theme.getFont("default")
text: catalog.i18nc("@label", "The following packages will be added:")
visible: toolbox.has_compatible_packages
color: UM.Theme.getColor("text")
height: contentHeight + UM.Theme.getSize("default_margin").height
}
@ -59,8 +60,8 @@ UM.Dialog{
Item
{
width: parent.width
property var lineHeight: 60
visible: model.is_compatible == "True" ? true : false
property int lineHeight: 60
visible: model.is_compatible
height: visible ? (lineHeight + UM.Theme.getSize("default_margin").height) : 0 // We only show the compatible packages here
Image
{
@ -90,6 +91,7 @@ UM.Dialog{
{
font: UM.Theme.getFont("default")
text: catalog.i18nc("@label", "The following packages can not be installed because of incompatible Cura version:")
visible: toolbox.has_incompatible_packages
color: UM.Theme.getColor("text")
height: contentHeight + UM.Theme.getSize("default_margin").height
}
@ -101,8 +103,8 @@ UM.Dialog{
Item
{
width: parent.width
property var lineHeight: 60
visible: model.is_compatible == "True" ? false : true
property int lineHeight: 60
visible: !model.is_compatible
height: visible ? (lineHeight + UM.Theme.getSize("default_margin").height) : 0 // We only show the incompatible packages here
Image
{

View File

@ -57,6 +57,7 @@ UM.Dialog
{
licenseDialog.close();
toolbox.install(licenseDialog.pluginFileLocation);
toolbox.subscribe(licenseDialog.pluginName);
}
},
Button

View File

@ -10,6 +10,7 @@ class SubscribedPackagesModel(ListModel):
def __init__(self, parent = None):
super().__init__(parent)
self._items = []
self._metadata = None
self._discrepancies = None
self._sdk_version = ApplicationMetadata.CuraSDKVersion
@ -27,20 +28,34 @@ class SubscribedPackagesModel(ListModel):
self._discrepancies = discrepancy
def update(self):
items = []
self._items.clear()
for item in self._metadata:
if item["package_id"] not in self._discrepancies:
continue
package = {"name": item["display_name"], "sdk_versions": item["sdk_versions"]}
if self._sdk_version not in item["sdk_versions"]:
package.update({"is_compatible": "False"})
package.update({"is_compatible": False})
else:
package.update({"is_compatible": "True"})
package.update({"is_compatible": True})
try:
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": ""})
items.append(package)
self.setItems(items)
self._items.append(package)
self.setItems(self._items)
def hasCompatiblePackages(self) -> bool:
has_compatible_items = False
for item in self._items:
if item['is_compatible'] == True:
has_compatible_items = True
return has_compatible_items
def hasIncompatiblePackages(self) -> bool:
has_incompatible_items = False
for item in self._items:
if item['is_compatible'] == False:
has_incompatible_items = True
return has_incompatible_items

View File

@ -164,6 +164,14 @@ class Toolbox(QObject, Extension):
self._application.getHttpRequestManager().put(url, headers_dict = self._request_headers,
data = data.encode())
@pyqtSlot(str)
def subscribe(self, package_id: str) -> None:
if self._application.getCuraAPI().account.isLoggedIn:
data = "{\"data\": {\"package_id\": \"%s\", \"sdk_version\": \"%s\"}}" % (package_id, self._sdk_version)
self._application.getHttpRequestManager().put(url=self._api_url_user_packages,
headers_dict=self._request_headers,
data=data.encode()
)
@pyqtSlot(result = str)
def getLicenseDialogPluginName(self) -> str:
@ -221,12 +229,6 @@ class Toolbox(QObject, Extension):
self._makeRequestByType("updates")
self._fetchUserSubscribedPackages()
@pyqtSlot()
def browsePackages(self) -> None:
# Make remote requests:
self._fetchPackageData()
# Gather installed packages:
self._updateInstalledModels()
def _fetchUserSubscribedPackages(self):
if self._application.getCuraAPI().account.isLoggedIn:
@ -235,6 +237,7 @@ class Toolbox(QObject, Extension):
def _fetchPackageData(self) -> None:
self._makeRequestByType("packages")
self._makeRequestByType("authors")
self._updateInstalledModels()
# Displays the toolbox
@pyqtSlot()
@ -733,6 +736,7 @@ class Toolbox(QObject, Extension):
return
self.install(file_path)
self.subscribe(package_info["package_id"])
# Getter & Setters for Properties:
# --------------------------------------------------------------------------
@ -792,6 +796,14 @@ class Toolbox(QObject, Extension):
def subscribedPackagesModel(self) -> SubscribedPackagesModel:
return cast(SubscribedPackagesModel, self._models["subscribed_packages"])
@pyqtProperty(bool, constant=True)
def has_compatible_packages(self) -> bool:
return self._models["subscribed_packages"].hasCompatiblePackages()
@pyqtProperty(bool, constant=True)
def has_incompatible_packages(self) -> bool:
return self._models["subscribed_packages"].hasIncompatiblePackages()
@pyqtProperty(QObject, constant = True)
def packagesModel(self) -> PackagesModel:
return cast(PackagesModel, self._models["packages"])

View File

@ -258,7 +258,7 @@
"support_interface_density": { "value": 33.333 },
"support_interface_pattern": { "value": "'grid'" },
"support_interface_skip_height": { "value": 0.2 },
"minimum_support_area": { "value": 5 },
"minimum_support_area": { "value": 2 },
"minimum_interface_area": { "value": 10 },
"top_bottom_thickness": {"value": "layer_height_0 + layer_height * 3" },
"wall_thickness": {"value": "line_width * 2" }

View File

@ -4521,6 +4521,7 @@
"type": "float",
"default_value": 0.0,
"minimum_value": "0",
"maximum_value_warning": "5",
"enabled": "support_enable",
"limit_to_extruder": "support_infill_extruder_nr",
"settable_per_mesh": true
@ -7416,7 +7417,7 @@
"clean_between_layers":
{
"label": "Wipe Nozzle Between Layers",
"description": "Whether to include nozzle wipe G-Code between layers. Enabling this setting could influence behavior of retract at layer change. Please use Wipe Retraction settings to control retraction at layers where the wipe script will be working.",
"description": "Whether to include nozzle wipe G-Code between layers (maximum 1 per layer). Enabling this setting could influence behavior of retract at layer change. Please use Wipe Retraction settings to control retraction at layers where the wipe script will be working.",
"default_value": false,
"type": "bool",
"settable_per_mesh": false,
@ -7426,7 +7427,7 @@
"max_extrusion_before_wipe":
{
"label": "Material Volume Between Wipes",
"description": "Maximum material, that can be extruded before another nozzle wipe is initiated.",
"description": "Maximum material that can be extruded before another nozzle wipe is initiated. If this value is less than the volume of material required in a layer, the setting has no effect in this layer, i.e. it is limited to one wipe per layer.",
"default_value": 10,
"type": "float",
"unit": "mm³",

View File

@ -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")