Merge branch 'master' of github.com:Ultimaker/Cura into CURA-5744-move-oauth-login

This commit is contained in:
Jaime van Kessel 2018-09-27 17:52:00 +02:00
commit 57334dd751
17 changed files with 196 additions and 99 deletions

40
Jenkinsfile vendored
View file

@ -52,12 +52,50 @@ parallel_nodes(['linux && cura', 'windows && cura']) {
// Try and run the unit tests. If this stage fails, we consider the build to be "unstable".
stage('Unit Test') {
if (isUnix()) {
// For Linux to show everything
def branch = env.BRANCH_NAME
if(!fileExists("${env.CURA_ENVIRONMENT_PATH}/${branch}")) {
branch = "master"
}
def uranium_dir = get_workspace_dir("Ultimaker/Uranium/${branch}")
try {
make('test')
sh """
cd ..
export PYTHONPATH=.:"${uranium_dir}"
${env.CURA_ENVIRONMENT_PATH}/${branch}/bin/pytest -x --verbose --full-trace --capture=no ./tests
"""
} catch(e) {
currentBuild.result = "UNSTABLE"
}
}
else {
// For Windows
make('test')
}
}
stage('Code Style') {
if (isUnix()) {
// For Linux to show everything
def branch = env.BRANCH_NAME
if(!fileExists("${env.CURA_ENVIRONMENT_PATH}/${branch}")) {
branch = "master"
}
def uranium_dir = get_workspace_dir("Ultimaker/Uranium/${branch}")
try {
sh """
cd ..
export PYTHONPATH=.:"${uranium_dir}"
${env.CURA_ENVIRONMENT_PATH}/${branch}/bin/python3 run_mypy.py
"""
} catch(e) {
currentBuild.result = "UNSTABLE"
}
}
}
}
}

View file

@ -34,7 +34,7 @@ function(cura_add_test)
if (NOT ${test_exists})
add_test(
NAME ${_NAME}
COMMAND ${PYTHON_EXECUTABLE} -m pytest --junitxml=${CMAKE_BINARY_DIR}/junit-${_NAME}.xml ${_DIRECTORY}
COMMAND ${PYTHON_EXECUTABLE} -m pytest --verbose --full-trace --capture=no --no-print-log --junitxml=${CMAKE_BINARY_DIR}/junit-${_NAME}.xml ${_DIRECTORY}
)
set_tests_properties(${_NAME} PROPERTIES ENVIRONMENT LANG=C)
set_tests_properties(${_NAME} PROPERTIES ENVIRONMENT "PYTHONPATH=${_PYTHONPATH}")

View file

@ -479,8 +479,6 @@ class BuildVolume(SceneNode):
maximum = Vector(max_w - bed_adhesion_size - 1, max_h - self._raft_thickness - self._extra_z_clearance, max_d - disallowed_area_size + bed_adhesion_size - 1)
)
self._application.getController().getScene()._maximum_bounds = scale_to_max_bounds
self.updateNodeBoundaryCheck()
def getBoundingBox(self) -> AxisAlignedBox:

View file

@ -9,9 +9,6 @@ from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
from UM.Logger import Logger
from UM.Settings.InstanceContainer import InstanceContainer
if TYPE_CHECKING:
from cura.Machines.QualityGroup import QualityGroup
##
# A metadata / container combination. Use getContainer() to get the container corresponding to the metadata.
@ -24,11 +21,11 @@ if TYPE_CHECKING:
# This is used in Variant, Material, and Quality Managers.
#
class ContainerNode:
__slots__ = ("_metadata", "container", "children_map")
__slots__ = ("_metadata", "_container", "children_map")
def __init__(self, metadata: Optional[Dict[str, Any]] = None) -> None:
self._metadata = metadata
self.container = None
self._container = None # type: Optional[InstanceContainer]
self.children_map = OrderedDict() # type: ignore # This is because it's children are supposed to override it.
## Get an entry value from the metadata
@ -50,7 +47,7 @@ class ContainerNode:
Logger.log("e", "Cannot get container for a ContainerNode without metadata.")
return None
if self.container is None:
if self._container is None:
container_id = self._metadata["id"]
from UM.Settings.ContainerRegistry import ContainerRegistry
container_list = ContainerRegistry.getInstance().findInstanceContainers(id = container_id)
@ -59,9 +56,9 @@ class ContainerNode:
error_message = ConfigurationErrorMessage.getInstance()
error_message.addFaultyContainers(container_id)
return None
self.container = container_list[0]
self._container = container_list[0]
return self.container
return self._container
def __str__(self) -> str:
return "%s[%s]" % (self.__class__.__name__, self.getMetaDataEntry("id"))

View file

@ -21,7 +21,6 @@ from .VariantType import VariantType
if TYPE_CHECKING:
from UM.Settings.DefinitionContainer import DefinitionContainer
from UM.Settings.InstanceContainer import InstanceContainer
from cura.Settings.GlobalStack import GlobalStack
from cura.Settings.ExtruderStack import ExtruderStack

View file

@ -58,7 +58,7 @@ class SettingVisibilityPresetsModel(ListModel):
break
return result
def _populate(self):
def _populate(self) -> None:
from cura.CuraApplication import CuraApplication
items = []
for file_path in Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.SettingVisibilityPreset):
@ -147,7 +147,7 @@ class SettingVisibilityPresetsModel(ListModel):
def activePreset(self) -> str:
return self._active_preset_item["id"]
def _onPreferencesChanged(self, name: str):
def _onPreferencesChanged(self, name: str) -> None:
if name != "general/visible_settings":
return

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 Optional, Dict, cast
from typing import Optional, Dict, cast, Any
from .ContainerNode import ContainerNode
from .QualityChangesGroup import QualityChangesGroup
@ -12,21 +12,21 @@ from .QualityChangesGroup import QualityChangesGroup
#
class QualityNode(ContainerNode):
def __init__(self, metadata: Optional[dict] = None) -> None:
def __init__(self, metadata: Optional[Dict[str, Any]] = None) -> None:
super().__init__(metadata = metadata)
self.quality_type_map = {} # type: Dict[str, QualityNode] # quality_type -> QualityNode for InstanceContainer
def getChildNode(self, child_key: str) -> Optional["QualityNode"]:
return self.children_map.get(child_key)
def addQualityMetadata(self, quality_type: str, metadata: dict):
def addQualityMetadata(self, quality_type: str, metadata: Dict[str, Any]):
if quality_type not in self.quality_type_map:
self.quality_type_map[quality_type] = QualityNode(metadata)
def getQualityNode(self, quality_type: str) -> Optional["QualityNode"]:
return self.quality_type_map.get(quality_type)
def addQualityChangesMetadata(self, quality_type: str, metadata: dict):
def addQualityChangesMetadata(self, quality_type: str, metadata: Dict[str, Any]):
if quality_type not in self.quality_type_map:
self.quality_type_map[quality_type] = QualityNode()
quality_type_node = self.quality_type_map[quality_type]

View file

@ -13,20 +13,20 @@ class ConfigurationModel(QObject):
configurationChanged = pyqtSignal()
def __init__(self):
def __init__(self) -> None:
super().__init__()
self._printer_type = None
self._printer_type = ""
self._extruder_configurations = [] # type: List[ExtruderConfigurationModel]
self._buildplate_configuration = None
self._buildplate_configuration = ""
def setPrinterType(self, printer_type):
self._printer_type = printer_type
@pyqtProperty(str, fset = setPrinterType, notify = configurationChanged)
def printerType(self):
def printerType(self) -> str:
return self._printer_type
def setExtruderConfigurations(self, extruder_configurations):
def setExtruderConfigurations(self, extruder_configurations: List["ExtruderConfigurationModel"]):
if self._extruder_configurations != extruder_configurations:
self._extruder_configurations = extruder_configurations
@ -39,16 +39,16 @@ class ConfigurationModel(QObject):
def extruderConfigurations(self):
return self._extruder_configurations
def setBuildplateConfiguration(self, buildplate_configuration):
def setBuildplateConfiguration(self, buildplate_configuration: str) -> None:
self._buildplate_configuration = buildplate_configuration
@pyqtProperty(str, fset = setBuildplateConfiguration, notify = configurationChanged)
def buildplateConfiguration(self):
def buildplateConfiguration(self) -> str:
return self._buildplate_configuration
## This method is intended to indicate whether the configuration is valid or not.
# The method checks if the mandatory fields are or not set
def isValid(self):
def isValid(self) -> bool:
if not self._extruder_configurations:
return False
for configuration in self._extruder_configurations:

View file

@ -53,21 +53,8 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
self._sending_gcode = False
self._compressing_gcode = False
self._gcode = [] # type: List[str]
self._connection_state_before_timeout = None # type: Optional[ConnectionState]
printer_type = self._properties.get(b"machine", b"").decode("utf-8")
printer_type_identifiers = {
"9066": "ultimaker3",
"9511": "ultimaker3_extended",
"9051": "ultimaker_s5"
}
self._printer_type = "Unknown"
for key, value in printer_type_identifiers.items():
if printer_type.startswith(key):
self._printer_type = value
break
def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs: str) -> None:
raise NotImplementedError("requestWrite needs to be implemented")
@ -341,7 +328,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
@pyqtProperty(str, constant = True)
def printerType(self) -> str:
return self._printer_type
return self._properties.get(b"printer_type", b"Unknown").decode("utf-8")
## IP adress of this printer
@pyqtProperty(str, constant = True)

View file

@ -13,23 +13,31 @@ from cura.Scene import ConvexHullNode
import numpy
from typing import TYPE_CHECKING, Any, Optional
if TYPE_CHECKING:
from UM.Scene.SceneNode import SceneNode
from cura.Settings.GlobalStack import GlobalStack
## The convex hull decorator is a scene node decorator that adds the convex hull functionality to a scene node.
# If a scene node has a convex hull decorator, it will have a shadow in which other objects can not be printed.
class ConvexHullDecorator(SceneNodeDecorator):
def __init__(self):
def __init__(self) -> None:
super().__init__()
self._convex_hull_node = None
self._convex_hull_node = None # type: Optional["SceneNode"]
self._init2DConvexHullCache()
self._global_stack = None
self._global_stack = None # type: Optional[GlobalStack]
# Make sure the timer is created on the main thread
self._recompute_convex_hull_timer = None
self._recompute_convex_hull_timer = None # type: Optional[QTimer]
if Application.getInstance() is not None:
Application.getInstance().callLater(self.createRecomputeConvexHullTimer)
self._raft_thickness = 0.0
# For raft thickness, DRY
self._build_volume = Application.getInstance().getBuildVolume()
self._build_volume.raftThicknessChanged.connect(self._onChanged)
@ -39,13 +47,13 @@ class ConvexHullDecorator(SceneNodeDecorator):
self._onGlobalStackChanged()
def createRecomputeConvexHullTimer(self):
def createRecomputeConvexHullTimer(self) -> None:
self._recompute_convex_hull_timer = QTimer()
self._recompute_convex_hull_timer.setInterval(200)
self._recompute_convex_hull_timer.setSingleShot(True)
self._recompute_convex_hull_timer.timeout.connect(self.recomputeConvexHull)
def setNode(self, node):
def setNode(self, node: "SceneNode") -> None:
previous_node = self._node
# Disconnect from previous node signals
if previous_node is not None and node is not previous_node:
@ -63,14 +71,14 @@ class ConvexHullDecorator(SceneNodeDecorator):
def __deepcopy__(self, memo):
return ConvexHullDecorator()
## Get the unmodified 2D projected convex hull of the node
def getConvexHull(self):
## Get the unmodified 2D projected convex hull of the node (if any)
def getConvexHull(self) -> Optional[Polygon]:
if self._node is None:
return None
hull = self._compute2DConvexHull()
if self._global_stack and self._node:
if self._global_stack and self._node and hull is not None:
# Parent can be None if node is just loaded.
if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and (self._node.getParent() is None or not self._node.getParent().callDecoration("isGroup")):
hull = hull.getMinkowskiHull(Polygon(numpy.array(self._global_stack.getProperty("machine_head_polygon", "value"), numpy.float32)))
@ -78,7 +86,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
return hull
## Get the convex hull of the node with the full head size
def getConvexHullHeadFull(self):
def getConvexHullHeadFull(self) -> Optional[Polygon]:
if self._node is None:
return None
@ -87,7 +95,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
## Get convex hull of the object + head size
# In case of printing all at once this is the same as the convex hull.
# For one at the time this is area with intersection of mirrored head
def getConvexHullHead(self):
def getConvexHullHead(self) -> Optional[Polygon]:
if self._node is None:
return None
@ -101,7 +109,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
## Get convex hull of the node
# In case of printing all at once this is the same as the convex hull.
# For one at the time this is the area without the head.
def getConvexHullBoundary(self):
def getConvexHullBoundary(self) -> Optional[Polygon]:
if self._node is None:
return None
@ -111,13 +119,14 @@ class ConvexHullDecorator(SceneNodeDecorator):
return self._compute2DConvexHull()
return None
def recomputeConvexHullDelayed(self):
## The same as recomputeConvexHull, but using a timer if it was set.
def recomputeConvexHullDelayed(self) -> None:
if self._recompute_convex_hull_timer is not None:
self._recompute_convex_hull_timer.start()
else:
self.recomputeConvexHull()
def recomputeConvexHull(self):
def recomputeConvexHull(self) -> None:
controller = Application.getInstance().getController()
root = controller.getScene().getRoot()
if self._node is None or controller.isToolOperationActive() or not self.__isDescendant(root, self._node):
@ -132,7 +141,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
hull_node = ConvexHullNode.ConvexHullNode(self._node, convex_hull, self._raft_thickness, root)
self._convex_hull_node = hull_node
def _onSettingValueChanged(self, key, property_name):
def _onSettingValueChanged(self, key: str, property_name: str) -> None:
if property_name != "value": # Not the value that was changed.
return
@ -142,7 +151,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
self._init2DConvexHullCache() # Invalidate the cache.
self._onChanged()
def _init2DConvexHullCache(self):
def _init2DConvexHullCache(self) -> None:
# Cache for the group code path in _compute2DConvexHull()
self._2d_convex_hull_group_child_polygon = None
self._2d_convex_hull_group_result = None
@ -152,7 +161,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
self._2d_convex_hull_mesh_world_transform = None
self._2d_convex_hull_mesh_result = None
def _compute2DConvexHull(self):
def _compute2DConvexHull(self) -> Optional[Polygon]:
if self._node.callDecoration("isGroup"):
points = numpy.zeros((0, 2), dtype=numpy.int32)
for child in self._node.getChildren():
@ -179,8 +188,6 @@ class ConvexHullDecorator(SceneNodeDecorator):
else:
offset_hull = None
mesh = None
world_transform = None
if self._node.getMeshData():
mesh = self._node.getMeshData()
world_transform = self._node.getWorldTransformation()
@ -228,24 +235,33 @@ class ConvexHullDecorator(SceneNodeDecorator):
return offset_hull
def _getHeadAndFans(self):
def _getHeadAndFans(self) -> Polygon:
if self._global_stack:
return Polygon(numpy.array(self._global_stack.getHeadAndFansCoordinates(), numpy.float32))
return Polygon()
def _compute2DConvexHeadFull(self):
return self._compute2DConvexHull().getMinkowskiHull(self._getHeadAndFans())
def _compute2DConvexHeadFull(self) -> Optional[Polygon]:
convex_hull = self._compute2DConvexHeadFull()
if convex_hull:
return convex_hull.getMinkowskiHull(self._getHeadAndFans())
return None
def _compute2DConvexHeadMin(self):
headAndFans = self._getHeadAndFans()
mirrored = headAndFans.mirror([0, 0], [0, 1]).mirror([0, 0], [1, 0]) # Mirror horizontally & vertically.
def _compute2DConvexHeadMin(self) -> Optional[Polygon]:
head_and_fans = self._getHeadAndFans()
mirrored = head_and_fans.mirror([0, 0], [0, 1]).mirror([0, 0], [1, 0]) # Mirror horizontally & vertically.
head_and_fans = self._getHeadAndFans().intersectionConvexHulls(mirrored)
# Min head hull is used for the push free
min_head_hull = self._compute2DConvexHull().getMinkowskiHull(head_and_fans)
return min_head_hull
convex_hull = self._compute2DConvexHeadFull()
if convex_hull:
return convex_hull.getMinkowskiHull(head_and_fans)
return None
## Compensate given 2D polygon with adhesion margin
# \return 2D polygon with added margin
def _add2DAdhesionMargin(self, poly):
def _add2DAdhesionMargin(self, poly: Polygon) -> Polygon:
if not self._global_stack:
return Polygon()
# Compensate for raft/skirt/brim
# Add extra margin depending on adhesion type
adhesion_type = self._global_stack.getProperty("adhesion_type", "value")
@ -263,7 +279,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
else:
raise Exception("Unknown bed adhesion type. Did you forget to update the convex hull calculations for your new bed adhesion type?")
# adjust head_and_fans with extra margin
# Adjust head_and_fans with extra margin
if extra_margin > 0:
extra_margin_polygon = Polygon.approximatedCircle(extra_margin)
poly = poly.getMinkowskiHull(extra_margin_polygon)
@ -274,7 +290,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
# \param convex_hull Polygon of the original convex hull.
# \return New Polygon instance that is offset with everything that
# influences the collision area.
def _offsetHull(self, convex_hull):
def _offsetHull(self, convex_hull: Polygon) -> Polygon:
horizontal_expansion = max(
self._getSettingProperty("xy_offset", "value"),
self._getSettingProperty("xy_offset_layer_0", "value")
@ -295,12 +311,12 @@ class ConvexHullDecorator(SceneNodeDecorator):
else:
return convex_hull
def _onChanged(self, *args):
def _onChanged(self, *args) -> None:
self._raft_thickness = self._build_volume.getRaftThickness()
if not args or args[0] == self._node:
self.recomputeConvexHullDelayed()
def _onGlobalStackChanged(self):
def _onGlobalStackChanged(self) -> None:
if self._global_stack:
self._global_stack.propertyChanged.disconnect(self._onSettingValueChanged)
self._global_stack.containersChanged.disconnect(self._onChanged)
@ -321,7 +337,9 @@ class ConvexHullDecorator(SceneNodeDecorator):
self._onChanged()
## Private convenience function to get a setting from the correct extruder (as defined by limit_to_extruder property).
def _getSettingProperty(self, setting_key, prop = "value"):
def _getSettingProperty(self, setting_key: str, prop: str = "value") -> Any:
if not self._global_stack:
return None
per_mesh_stack = self._node.callDecoration("getStack")
if per_mesh_stack:
return per_mesh_stack.getProperty(setting_key, prop)
@ -339,8 +357,8 @@ class ConvexHullDecorator(SceneNodeDecorator):
# Limit_to_extruder is set. The global stack handles this then
return self._global_stack.getProperty(setting_key, prop)
## Returns true if node is a descendant or the same as the root node.
def __isDescendant(self, root, node):
## Returns True if node is a descendant or the same as the root node.
def __isDescendant(self, root: "SceneNode", node: "SceneNode") -> bool:
if node is None:
return False
if root is node:

View file

@ -1,13 +1,19 @@
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
from typing import List
class GCodeListDecorator(SceneNodeDecorator):
def __init__(self):
def __init__(self) -> None:
super().__init__()
self._gcode_list = []
self._gcode_list = [] # type: List[str]
def getGCodeList(self):
def getGCodeList(self) -> List[str]:
return self._gcode_list
def setGCodeList(self, list):
def setGCodeList(self, list: List[str]):
self._gcode_list = list
def __deepcopy__(self, memo) -> "GCodeListDecorator":
copied_decorator = GCodeListDecorator()
copied_decorator.setGCodeList(self.getGCodeList())
return copied_decorator

View file

@ -2,11 +2,11 @@ from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
class SliceableObjectDecorator(SceneNodeDecorator):
def __init__(self):
def __init__(self) -> None:
super().__init__()
def isSliceable(self):
def isSliceable(self) -> bool:
return True
def __deepcopy__(self, memo):
def __deepcopy__(self, memo) -> "SliceableObjectDecorator":
return type(self)()

View file

@ -1,18 +1,19 @@
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
## A decorator that stores the amount an object has been moved below the platform.
class ZOffsetDecorator(SceneNodeDecorator):
def __init__(self):
def __init__(self) -> None:
super().__init__()
self._z_offset = 0
self._z_offset = 0.
def setZOffset(self, offset):
def setZOffset(self, offset: float) -> None:
self._z_offset = offset
def getZOffset(self):
def getZOffset(self) -> float:
return self._z_offset
def __deepcopy__(self, memo):
def __deepcopy__(self, memo) -> "ZOffsetDecorator":
copied_decorator = ZOffsetDecorator()
copied_decorator.setZOffset(self.getZOffset())
return copied_decorator

View file

@ -23,6 +23,7 @@ from .CuraContainerStack import CuraContainerStack
if TYPE_CHECKING:
from cura.Settings.ExtruderStack import ExtruderStack
## Represents the Global or Machine stack and its related containers.
#
class GlobalStack(CuraContainerStack):

View file

@ -260,6 +260,19 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
# or "Legacy" UM3 device.
cluster_size = int(properties.get(b"cluster_size", -1))
printer_type = properties.get(b"machine", b"").decode("utf-8")
printer_type_identifiers = {
"9066": "ultimaker3",
"9511": "ultimaker3_extended",
"9051": "ultimaker_s5"
}
for key, value in printer_type_identifiers.items():
if printer_type.startswith(key):
properties[b"printer_type"] = bytes(value, encoding="utf8")
break
else:
properties[b"printer_type"] = b"Unknown"
if cluster_size >= 0:
device = ClusterUM3OutputDevice.ClusterUM3OutputDevice(name, address, properties)
else:

View file

@ -15,6 +15,7 @@ import UM.Settings.SettingDefinition #To add settings to the definition.
from cura.Settings.cura_empty_instance_containers import empty_container
## Gets an instance container with a specified container type.
#
# \param container_type The type metadata for the instance container.
@ -24,22 +25,27 @@ def getInstanceContainer(container_type) -> InstanceContainer:
container.setMetaDataEntry("type", container_type)
return container
class DefinitionContainerSubClass(DefinitionContainer):
def __init__(self):
super().__init__(container_id = "SubDefinitionContainer")
class InstanceContainerSubClass(InstanceContainer):
def __init__(self, container_type):
super().__init__(container_id = "SubInstanceContainer")
self.setMetaDataEntry("type", container_type)
#############################START OF TEST CASES################################
## Tests whether adding a container is properly forbidden.
def test_addContainer(global_stack):
with pytest.raises(InvalidOperationError):
global_stack.addContainer(unittest.mock.MagicMock())
## Tests adding extruders to the global stack.
def test_addExtruder(global_stack):
mock_definition = unittest.mock.MagicMock()
@ -67,6 +73,7 @@ def test_addExtruder(global_stack):
# global_stack.addExtruder(unittest.mock.MagicMock())
assert len(global_stack.extruders) == 2 #Didn't add the faulty extruder.
#Tests setting user changes profiles to invalid containers.
@pytest.mark.parametrize("container", [
getInstanceContainer(container_type = "wrong container type"),
@ -77,6 +84,7 @@ def test_constrainUserChangesInvalid(container, global_stack):
with pytest.raises(InvalidContainerError): #Invalid container, should raise an error.
global_stack.userChanges = container
#Tests setting user changes profiles.
@pytest.mark.parametrize("container", [
getInstanceContainer(container_type = "user"),
@ -85,6 +93,7 @@ def test_constrainUserChangesInvalid(container, global_stack):
def test_constrainUserChangesValid(container, global_stack):
global_stack.userChanges = container #Should not give an error.
#Tests setting quality changes profiles to invalid containers.
@pytest.mark.parametrize("container", [
getInstanceContainer(container_type = "wrong container type"),
@ -95,6 +104,7 @@ def test_constrainQualityChangesInvalid(container, global_stack):
with pytest.raises(InvalidContainerError): #Invalid container, should raise an error.
global_stack.qualityChanges = container
#Test setting quality changes profiles.
@pytest.mark.parametrize("container", [
getInstanceContainer(container_type = "quality_changes"),
@ -103,6 +113,7 @@ def test_constrainQualityChangesInvalid(container, global_stack):
def test_constrainQualityChangesValid(container, global_stack):
global_stack.qualityChanges = container #Should not give an error.
#Tests setting quality profiles to invalid containers.
@pytest.mark.parametrize("container", [
getInstanceContainer(container_type = "wrong container type"),
@ -113,6 +124,7 @@ def test_constrainQualityInvalid(container, global_stack):
with pytest.raises(InvalidContainerError): #Invalid container, should raise an error.
global_stack.quality = container
#Test setting quality profiles.
@pytest.mark.parametrize("container", [
getInstanceContainer(container_type = "quality"),
@ -121,6 +133,7 @@ def test_constrainQualityInvalid(container, global_stack):
def test_constrainQualityValid(container, global_stack):
global_stack.quality = container #Should not give an error.
#Tests setting materials to invalid containers.
@pytest.mark.parametrize("container", [
getInstanceContainer(container_type = "wrong container type"),
@ -131,6 +144,7 @@ def test_constrainMaterialInvalid(container, global_stack):
with pytest.raises(InvalidContainerError): #Invalid container, should raise an error.
global_stack.material = container
#Test setting materials.
@pytest.mark.parametrize("container", [
getInstanceContainer(container_type = "material"),
@ -139,6 +153,7 @@ def test_constrainMaterialInvalid(container, global_stack):
def test_constrainMaterialValid(container, global_stack):
global_stack.material = container #Should not give an error.
#Tests setting variants to invalid containers.
@pytest.mark.parametrize("container", [
getInstanceContainer(container_type = "wrong container type"),
@ -149,6 +164,7 @@ def test_constrainVariantInvalid(container, global_stack):
with pytest.raises(InvalidContainerError): #Invalid container, should raise an error.
global_stack.variant = container
#Test setting variants.
@pytest.mark.parametrize("container", [
getInstanceContainer(container_type = "variant"),
@ -157,6 +173,7 @@ def test_constrainVariantInvalid(container, global_stack):
def test_constrainVariantValid(container, global_stack):
global_stack.variant = container #Should not give an error.
#Tests setting definition changes profiles to invalid containers.
@pytest.mark.parametrize("container", [
getInstanceContainer(container_type = "wrong container type"),
@ -167,6 +184,7 @@ def test_constrainDefinitionChangesInvalid(container, global_stack):
with pytest.raises(InvalidContainerError): #Invalid container, should raise an error.
global_stack.definitionChanges = container
#Test setting definition changes profiles.
@pytest.mark.parametrize("container", [
getInstanceContainer(container_type = "definition_changes"),
@ -175,6 +193,7 @@ def test_constrainDefinitionChangesInvalid(container, global_stack):
def test_constrainDefinitionChangesValid(container, global_stack):
global_stack.definitionChanges = container #Should not give an error.
#Tests setting definitions to invalid containers.
@pytest.mark.parametrize("container", [
getInstanceContainer(container_type = "wrong class"),
@ -184,6 +203,7 @@ def test_constrainDefinitionInvalid(container, global_stack):
with pytest.raises(InvalidContainerError): #Invalid container, should raise an error.
global_stack.definition = container
#Test setting definitions.
@pytest.mark.parametrize("container", [
DefinitionContainer(container_id = "DefinitionContainer"),
@ -192,6 +212,7 @@ def test_constrainDefinitionInvalid(container, global_stack):
def test_constrainDefinitionValid(container, global_stack):
global_stack.definition = container #Should not give an error.
## Tests whether deserialising completes the missing containers with empty ones. The initial containers are just the
# definition and the definition_changes (that cannot be empty after CURA-5281)
def test_deserializeCompletesEmptyContainers(global_stack):
@ -207,6 +228,7 @@ def test_deserializeCompletesEmptyContainers(global_stack):
continue
assert global_stack.getContainer(container_type_index) == empty_container #All others need to be empty.
## Tests whether an instance container with the wrong type gets removed when deserialising.
def test_deserializeRemovesWrongInstanceContainer(global_stack):
global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Quality] = getInstanceContainer(container_type = "wrong type")
@ -217,6 +239,7 @@ def test_deserializeRemovesWrongInstanceContainer(global_stack):
assert global_stack.quality == global_stack._empty_instance_container #Replaced with empty.
## Tests whether a container with the wrong class gets removed when deserialising.
def test_deserializeRemovesWrongContainerClass(global_stack):
global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Quality] = DefinitionContainer(container_id = "wrong class")
@ -227,6 +250,7 @@ def test_deserializeRemovesWrongContainerClass(global_stack):
assert global_stack.quality == global_stack._empty_instance_container #Replaced with empty.
## Tests whether an instance container in the definition spot results in an error.
def test_deserializeWrongDefinitionClass(global_stack):
global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = getInstanceContainer(container_type = "definition") #Correct type but wrong class.
@ -235,6 +259,7 @@ def test_deserializeWrongDefinitionClass(global_stack):
with pytest.raises(UM.Settings.ContainerStack.InvalidContainerStackError): #Must raise an error that there is no definition container.
global_stack.deserialize("")
## Tests whether an instance container with the wrong type is moved into the correct slot by deserialising.
def test_deserializeMoveInstanceContainer(global_stack):
global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Quality] = getInstanceContainer(container_type = "material") #Not in the correct spot.
@ -246,6 +271,7 @@ def test_deserializeMoveInstanceContainer(global_stack):
assert global_stack.quality == empty_container
assert global_stack.material != empty_container
## Tests whether a definition container in the wrong spot is moved into the correct spot by deserialising.
def test_deserializeMoveDefinitionContainer(global_stack):
global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Material] = DefinitionContainer(container_id = "some definition") #Not in the correct spot.
@ -256,6 +282,7 @@ def test_deserializeMoveDefinitionContainer(global_stack):
assert global_stack.material == empty_container
assert global_stack.definition != empty_container
## Tests whether getProperty properly applies the stack-like behaviour on its containers.
def test_getPropertyFallThrough(global_stack):
#A few instance container mocks to put in the stack.
@ -298,6 +325,7 @@ def test_getPropertyFallThrough(global_stack):
global_stack.userChanges = mock_layer_heights[container_indexes.UserChanges]
assert global_stack.getProperty("layer_height", "value") == container_indexes.UserChanges
## In definitions, test whether having no resolve allows us to find the value.
def test_getPropertyNoResolveInDefinition(global_stack):
value = unittest.mock.MagicMock() #Just sets the value for bed temperature.
@ -307,6 +335,7 @@ def test_getPropertyNoResolveInDefinition(global_stack):
global_stack.definition = value
assert global_stack.getProperty("material_bed_temperature", "value") == 10 #No resolve, so fall through to value.
## In definitions, when the value is asked and there is a resolve function, it must get the resolve first.
def test_getPropertyResolveInDefinition(global_stack):
resolve_and_value = unittest.mock.MagicMock() #Sets the resolve and value for bed temperature.
@ -316,6 +345,7 @@ def test_getPropertyResolveInDefinition(global_stack):
global_stack.definition = resolve_and_value
assert global_stack.getProperty("material_bed_temperature", "value") == 7.5 #Resolve wins in the definition.
## In instance containers, when the value is asked and there is a resolve function, it must get the value first.
def test_getPropertyResolveInInstance(global_stack):
container_indices = cura.Settings.CuraContainerStack._ContainerIndexes
@ -342,6 +372,7 @@ def test_getPropertyResolveInInstance(global_stack):
global_stack.userChanges = instance_containers[container_indices.UserChanges]
assert global_stack.getProperty("material_bed_temperature", "value") == 5
## Tests whether the value in instances gets evaluated before the resolve in definitions.
def test_getPropertyInstancesBeforeResolve(global_stack):
value = unittest.mock.MagicMock() #Sets just the value.
@ -356,6 +387,7 @@ def test_getPropertyInstancesBeforeResolve(global_stack):
assert global_stack.getProperty("material_bed_temperature", "value") == 10
## Tests whether the hasUserValue returns true for settings that are changed in the user-changes container.
def test_hasUserValueUserChanges(global_stack):
container = unittest.mock.MagicMock()
@ -367,6 +399,7 @@ def test_hasUserValueUserChanges(global_stack):
assert not global_stack.hasUserValue("infill_sparse_density")
assert not global_stack.hasUserValue("")
## Tests whether the hasUserValue returns true for settings that are changed in the quality-changes container.
def test_hasUserValueQualityChanges(global_stack):
container = unittest.mock.MagicMock()
@ -378,6 +411,7 @@ def test_hasUserValueQualityChanges(global_stack):
assert not global_stack.hasUserValue("infill_sparse_density")
assert not global_stack.hasUserValue("")
## Tests whether a container in some other place on the stack is correctly not recognised as user value.
def test_hasNoUserValue(global_stack):
container = unittest.mock.MagicMock()
@ -387,21 +421,25 @@ def test_hasNoUserValue(global_stack):
assert not global_stack.hasUserValue("layer_height") #However this container is quality, so it's not a user value.
## Tests whether inserting a container is properly forbidden.
def test_insertContainer(global_stack):
with pytest.raises(InvalidOperationError):
global_stack.insertContainer(0, unittest.mock.MagicMock())
## Tests whether removing a container is properly forbidden.
def test_removeContainer(global_stack):
with pytest.raises(InvalidOperationError):
global_stack.removeContainer(unittest.mock.MagicMock())
## Tests whether changing the next stack is properly forbidden.
def test_setNextStack(global_stack):
with pytest.raises(InvalidOperationError):
global_stack.setNextStack(unittest.mock.MagicMock())
## Tests setting properties directly on the global stack.
@pytest.mark.parametrize("key, property, value", [
("layer_height", "value", 0.1337),
@ -417,4 +455,5 @@ def test_setPropertyUser(key, property, value, global_stack):
global_stack.setProperty(key, property, value) # The actual test.
global_stack.userChanges.setProperty.assert_called_once_with(key, property, value, None, False) #Make sure that the user container gets a setProperty call.
# Make sure that the user container gets a setProperty call.
global_stack.userChanges.setProperty.assert_called_once_with(key, property, value, None, False)