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

44
Jenkinsfile vendored
View file

@ -52,10 +52,48 @@ parallel_nodes(['linux && cura', 'windows && cura']) {
// Try and run the unit tests. If this stage fails, we consider the build to be "unstable". // Try and run the unit tests. If this stage fails, we consider the build to be "unstable".
stage('Unit Test') { stage('Unit Test') {
try { 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/pytest -x --verbose --full-trace --capture=no ./tests
"""
} catch(e) {
currentBuild.result = "UNSTABLE"
}
}
else {
// For Windows
make('test') make('test')
} catch(e) { }
currentBuild.result = "UNSTABLE" }
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}) if (NOT ${test_exists})
add_test( add_test(
NAME ${_NAME} 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 LANG=C)
set_tests_properties(${_NAME} PROPERTIES ENVIRONMENT "PYTHONPATH=${_PYTHONPATH}") 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) 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() self.updateNodeBoundaryCheck()
def getBoundingBox(self) -> AxisAlignedBox: def getBoundingBox(self) -> AxisAlignedBox:

View file

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

View file

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

View file

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

View file

@ -24,9 +24,9 @@ class QualityChangesGroup(QualityGroup):
ConfigurationErrorMessage.getInstance().addFaultyContainers(node.getMetaDataEntry("id")) ConfigurationErrorMessage.getInstance().addFaultyContainers(node.getMetaDataEntry("id"))
return return
if extruder_position is None: #Then we're a global quality changes profile. if extruder_position is None: # Then we're a global quality changes profile.
self.node_for_global = node self.node_for_global = node
else: #This is an extruder's quality changes profile. else: # This is an extruder's quality changes profile.
self.nodes_for_extruders[extruder_position] = node self.nodes_for_extruders[extruder_position] = node
def __str__(self) -> str: def __str__(self) -> str:

View file

@ -1,7 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # 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 .ContainerNode import ContainerNode
from .QualityChangesGroup import QualityChangesGroup from .QualityChangesGroup import QualityChangesGroup
@ -12,21 +12,21 @@ from .QualityChangesGroup import QualityChangesGroup
# #
class QualityNode(ContainerNode): 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) super().__init__(metadata = metadata)
self.quality_type_map = {} # type: Dict[str, QualityNode] # quality_type -> QualityNode for InstanceContainer self.quality_type_map = {} # type: Dict[str, QualityNode] # quality_type -> QualityNode for InstanceContainer
def getChildNode(self, child_key: str) -> Optional["QualityNode"]: def getChildNode(self, child_key: str) -> Optional["QualityNode"]:
return self.children_map.get(child_key) 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: if quality_type not in self.quality_type_map:
self.quality_type_map[quality_type] = QualityNode(metadata) self.quality_type_map[quality_type] = QualityNode(metadata)
def getQualityNode(self, quality_type: str) -> Optional["QualityNode"]: def getQualityNode(self, quality_type: str) -> Optional["QualityNode"]:
return self.quality_type_map.get(quality_type) 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: if quality_type not in self.quality_type_map:
self.quality_type_map[quality_type] = QualityNode() self.quality_type_map[quality_type] = QualityNode()
quality_type_node = self.quality_type_map[quality_type] quality_type_node = self.quality_type_map[quality_type]

View file

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

View file

@ -53,21 +53,8 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
self._sending_gcode = False self._sending_gcode = False
self._compressing_gcode = False self._compressing_gcode = False
self._gcode = [] # type: List[str] self._gcode = [] # type: List[str]
self._connection_state_before_timeout = None # type: Optional[ConnectionState] 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: 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") raise NotImplementedError("requestWrite needs to be implemented")
@ -341,7 +328,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
@pyqtProperty(str, constant = True) @pyqtProperty(str, constant = True)
def printerType(self) -> str: 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 ## IP adress of this printer
@pyqtProperty(str, constant = True) @pyqtProperty(str, constant = True)

View file

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

View file

@ -1,13 +1,19 @@
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
from typing import List
class GCodeListDecorator(SceneNodeDecorator): class GCodeListDecorator(SceneNodeDecorator):
def __init__(self): def __init__(self) -> None:
super().__init__() super().__init__()
self._gcode_list = [] self._gcode_list = [] # type: List[str]
def getGCodeList(self): def getGCodeList(self) -> List[str]:
return self._gcode_list return self._gcode_list
def setGCodeList(self, list): def setGCodeList(self, list: List[str]):
self._gcode_list = list 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): class SliceableObjectDecorator(SceneNodeDecorator):
def __init__(self): def __init__(self) -> None:
super().__init__() super().__init__()
def isSliceable(self): def isSliceable(self) -> bool:
return True return True
def __deepcopy__(self, memo): def __deepcopy__(self, memo) -> "SliceableObjectDecorator":
return type(self)() return type(self)()

View file

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

View file

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

View file

@ -260,6 +260,19 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
# or "Legacy" UM3 device. # or "Legacy" UM3 device.
cluster_size = int(properties.get(b"cluster_size", -1)) 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: if cluster_size >= 0:
device = ClusterUM3OutputDevice.ClusterUM3OutputDevice(name, address, properties) device = ClusterUM3OutputDevice.ClusterUM3OutputDevice(name, address, properties)
else: 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 from cura.Settings.cura_empty_instance_containers import empty_container
## Gets an instance container with a specified container type. ## Gets an instance container with a specified container type.
# #
# \param container_type The type metadata for the instance container. # \param container_type The type metadata for the instance container.
@ -24,22 +25,27 @@ def getInstanceContainer(container_type) -> InstanceContainer:
container.setMetaDataEntry("type", container_type) container.setMetaDataEntry("type", container_type)
return container return container
class DefinitionContainerSubClass(DefinitionContainer): class DefinitionContainerSubClass(DefinitionContainer):
def __init__(self): def __init__(self):
super().__init__(container_id = "SubDefinitionContainer") super().__init__(container_id = "SubDefinitionContainer")
class InstanceContainerSubClass(InstanceContainer): class InstanceContainerSubClass(InstanceContainer):
def __init__(self, container_type): def __init__(self, container_type):
super().__init__(container_id = "SubInstanceContainer") super().__init__(container_id = "SubInstanceContainer")
self.setMetaDataEntry("type", container_type) self.setMetaDataEntry("type", container_type)
#############################START OF TEST CASES################################ #############################START OF TEST CASES################################
## Tests whether adding a container is properly forbidden. ## Tests whether adding a container is properly forbidden.
def test_addContainer(global_stack): def test_addContainer(global_stack):
with pytest.raises(InvalidOperationError): with pytest.raises(InvalidOperationError):
global_stack.addContainer(unittest.mock.MagicMock()) global_stack.addContainer(unittest.mock.MagicMock())
## Tests adding extruders to the global stack. ## Tests adding extruders to the global stack.
def test_addExtruder(global_stack): def test_addExtruder(global_stack):
mock_definition = unittest.mock.MagicMock() mock_definition = unittest.mock.MagicMock()
@ -67,6 +73,7 @@ def test_addExtruder(global_stack):
# global_stack.addExtruder(unittest.mock.MagicMock()) # global_stack.addExtruder(unittest.mock.MagicMock())
assert len(global_stack.extruders) == 2 #Didn't add the faulty extruder. assert len(global_stack.extruders) == 2 #Didn't add the faulty extruder.
#Tests setting user changes profiles to invalid containers. #Tests setting user changes profiles to invalid containers.
@pytest.mark.parametrize("container", [ @pytest.mark.parametrize("container", [
getInstanceContainer(container_type = "wrong container type"), 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. with pytest.raises(InvalidContainerError): #Invalid container, should raise an error.
global_stack.userChanges = container global_stack.userChanges = container
#Tests setting user changes profiles. #Tests setting user changes profiles.
@pytest.mark.parametrize("container", [ @pytest.mark.parametrize("container", [
getInstanceContainer(container_type = "user"), getInstanceContainer(container_type = "user"),
@ -85,6 +93,7 @@ def test_constrainUserChangesInvalid(container, global_stack):
def test_constrainUserChangesValid(container, global_stack): def test_constrainUserChangesValid(container, global_stack):
global_stack.userChanges = container #Should not give an error. global_stack.userChanges = container #Should not give an error.
#Tests setting quality changes profiles to invalid containers. #Tests setting quality changes profiles to invalid containers.
@pytest.mark.parametrize("container", [ @pytest.mark.parametrize("container", [
getInstanceContainer(container_type = "wrong container type"), 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. with pytest.raises(InvalidContainerError): #Invalid container, should raise an error.
global_stack.qualityChanges = container global_stack.qualityChanges = container
#Test setting quality changes profiles. #Test setting quality changes profiles.
@pytest.mark.parametrize("container", [ @pytest.mark.parametrize("container", [
getInstanceContainer(container_type = "quality_changes"), getInstanceContainer(container_type = "quality_changes"),
@ -103,6 +113,7 @@ def test_constrainQualityChangesInvalid(container, global_stack):
def test_constrainQualityChangesValid(container, global_stack): def test_constrainQualityChangesValid(container, global_stack):
global_stack.qualityChanges = container #Should not give an error. global_stack.qualityChanges = container #Should not give an error.
#Tests setting quality profiles to invalid containers. #Tests setting quality profiles to invalid containers.
@pytest.mark.parametrize("container", [ @pytest.mark.parametrize("container", [
getInstanceContainer(container_type = "wrong container type"), 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. with pytest.raises(InvalidContainerError): #Invalid container, should raise an error.
global_stack.quality = container global_stack.quality = container
#Test setting quality profiles. #Test setting quality profiles.
@pytest.mark.parametrize("container", [ @pytest.mark.parametrize("container", [
getInstanceContainer(container_type = "quality"), getInstanceContainer(container_type = "quality"),
@ -121,6 +133,7 @@ def test_constrainQualityInvalid(container, global_stack):
def test_constrainQualityValid(container, global_stack): def test_constrainQualityValid(container, global_stack):
global_stack.quality = container #Should not give an error. global_stack.quality = container #Should not give an error.
#Tests setting materials to invalid containers. #Tests setting materials to invalid containers.
@pytest.mark.parametrize("container", [ @pytest.mark.parametrize("container", [
getInstanceContainer(container_type = "wrong container type"), 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. with pytest.raises(InvalidContainerError): #Invalid container, should raise an error.
global_stack.material = container global_stack.material = container
#Test setting materials. #Test setting materials.
@pytest.mark.parametrize("container", [ @pytest.mark.parametrize("container", [
getInstanceContainer(container_type = "material"), getInstanceContainer(container_type = "material"),
@ -139,6 +153,7 @@ def test_constrainMaterialInvalid(container, global_stack):
def test_constrainMaterialValid(container, global_stack): def test_constrainMaterialValid(container, global_stack):
global_stack.material = container #Should not give an error. global_stack.material = container #Should not give an error.
#Tests setting variants to invalid containers. #Tests setting variants to invalid containers.
@pytest.mark.parametrize("container", [ @pytest.mark.parametrize("container", [
getInstanceContainer(container_type = "wrong container type"), 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. with pytest.raises(InvalidContainerError): #Invalid container, should raise an error.
global_stack.variant = container global_stack.variant = container
#Test setting variants. #Test setting variants.
@pytest.mark.parametrize("container", [ @pytest.mark.parametrize("container", [
getInstanceContainer(container_type = "variant"), getInstanceContainer(container_type = "variant"),
@ -157,6 +173,7 @@ def test_constrainVariantInvalid(container, global_stack):
def test_constrainVariantValid(container, global_stack): def test_constrainVariantValid(container, global_stack):
global_stack.variant = container #Should not give an error. global_stack.variant = container #Should not give an error.
#Tests setting definition changes profiles to invalid containers. #Tests setting definition changes profiles to invalid containers.
@pytest.mark.parametrize("container", [ @pytest.mark.parametrize("container", [
getInstanceContainer(container_type = "wrong container type"), 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. with pytest.raises(InvalidContainerError): #Invalid container, should raise an error.
global_stack.definitionChanges = container global_stack.definitionChanges = container
#Test setting definition changes profiles. #Test setting definition changes profiles.
@pytest.mark.parametrize("container", [ @pytest.mark.parametrize("container", [
getInstanceContainer(container_type = "definition_changes"), getInstanceContainer(container_type = "definition_changes"),
@ -175,6 +193,7 @@ def test_constrainDefinitionChangesInvalid(container, global_stack):
def test_constrainDefinitionChangesValid(container, global_stack): def test_constrainDefinitionChangesValid(container, global_stack):
global_stack.definitionChanges = container #Should not give an error. global_stack.definitionChanges = container #Should not give an error.
#Tests setting definitions to invalid containers. #Tests setting definitions to invalid containers.
@pytest.mark.parametrize("container", [ @pytest.mark.parametrize("container", [
getInstanceContainer(container_type = "wrong class"), 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. with pytest.raises(InvalidContainerError): #Invalid container, should raise an error.
global_stack.definition = container global_stack.definition = container
#Test setting definitions. #Test setting definitions.
@pytest.mark.parametrize("container", [ @pytest.mark.parametrize("container", [
DefinitionContainer(container_id = "DefinitionContainer"), DefinitionContainer(container_id = "DefinitionContainer"),
@ -192,6 +212,7 @@ def test_constrainDefinitionInvalid(container, global_stack):
def test_constrainDefinitionValid(container, global_stack): def test_constrainDefinitionValid(container, global_stack):
global_stack.definition = container #Should not give an error. 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 ## 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) # definition and the definition_changes (that cannot be empty after CURA-5281)
def test_deserializeCompletesEmptyContainers(global_stack): def test_deserializeCompletesEmptyContainers(global_stack):
@ -207,6 +228,7 @@ def test_deserializeCompletesEmptyContainers(global_stack):
continue continue
assert global_stack.getContainer(container_type_index) == empty_container #All others need to be empty. 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. ## Tests whether an instance container with the wrong type gets removed when deserialising.
def test_deserializeRemovesWrongInstanceContainer(global_stack): def test_deserializeRemovesWrongInstanceContainer(global_stack):
global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Quality] = getInstanceContainer(container_type = "wrong type") 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. assert global_stack.quality == global_stack._empty_instance_container #Replaced with empty.
## Tests whether a container with the wrong class gets removed when deserialising. ## Tests whether a container with the wrong class gets removed when deserialising.
def test_deserializeRemovesWrongContainerClass(global_stack): def test_deserializeRemovesWrongContainerClass(global_stack):
global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Quality] = DefinitionContainer(container_id = "wrong class") 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. 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. ## Tests whether an instance container in the definition spot results in an error.
def test_deserializeWrongDefinitionClass(global_stack): def test_deserializeWrongDefinitionClass(global_stack):
global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = getInstanceContainer(container_type = "definition") #Correct type but wrong class. 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. with pytest.raises(UM.Settings.ContainerStack.InvalidContainerStackError): #Must raise an error that there is no definition container.
global_stack.deserialize("") global_stack.deserialize("")
## Tests whether an instance container with the wrong type is moved into the correct slot by deserialising. ## Tests whether an instance container with the wrong type is moved into the correct slot by deserialising.
def test_deserializeMoveInstanceContainer(global_stack): def test_deserializeMoveInstanceContainer(global_stack):
global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Quality] = getInstanceContainer(container_type = "material") #Not in the correct spot. 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.quality == empty_container
assert global_stack.material != 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. ## Tests whether a definition container in the wrong spot is moved into the correct spot by deserialising.
def test_deserializeMoveDefinitionContainer(global_stack): def test_deserializeMoveDefinitionContainer(global_stack):
global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Material] = DefinitionContainer(container_id = "some definition") #Not in the correct spot. 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.material == empty_container
assert global_stack.definition != empty_container assert global_stack.definition != empty_container
## Tests whether getProperty properly applies the stack-like behaviour on its containers. ## Tests whether getProperty properly applies the stack-like behaviour on its containers.
def test_getPropertyFallThrough(global_stack): def test_getPropertyFallThrough(global_stack):
#A few instance container mocks to put in the 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] global_stack.userChanges = mock_layer_heights[container_indexes.UserChanges]
assert global_stack.getProperty("layer_height", "value") == 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. ## In definitions, test whether having no resolve allows us to find the value.
def test_getPropertyNoResolveInDefinition(global_stack): def test_getPropertyNoResolveInDefinition(global_stack):
value = unittest.mock.MagicMock() #Just sets the value for bed temperature. value = unittest.mock.MagicMock() #Just sets the value for bed temperature.
@ -307,6 +335,7 @@ def test_getPropertyNoResolveInDefinition(global_stack):
global_stack.definition = value global_stack.definition = value
assert global_stack.getProperty("material_bed_temperature", "value") == 10 #No resolve, so fall through to 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. ## In definitions, when the value is asked and there is a resolve function, it must get the resolve first.
def test_getPropertyResolveInDefinition(global_stack): def test_getPropertyResolveInDefinition(global_stack):
resolve_and_value = unittest.mock.MagicMock() #Sets the resolve and value for bed temperature. 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 global_stack.definition = resolve_and_value
assert global_stack.getProperty("material_bed_temperature", "value") == 7.5 #Resolve wins in the definition. 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. ## 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): def test_getPropertyResolveInInstance(global_stack):
container_indices = cura.Settings.CuraContainerStack._ContainerIndexes container_indices = cura.Settings.CuraContainerStack._ContainerIndexes
@ -342,6 +372,7 @@ def test_getPropertyResolveInInstance(global_stack):
global_stack.userChanges = instance_containers[container_indices.UserChanges] global_stack.userChanges = instance_containers[container_indices.UserChanges]
assert global_stack.getProperty("material_bed_temperature", "value") == 5 assert global_stack.getProperty("material_bed_temperature", "value") == 5
## Tests whether the value in instances gets evaluated before the resolve in definitions. ## Tests whether the value in instances gets evaluated before the resolve in definitions.
def test_getPropertyInstancesBeforeResolve(global_stack): def test_getPropertyInstancesBeforeResolve(global_stack):
value = unittest.mock.MagicMock() #Sets just the value. 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 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. ## Tests whether the hasUserValue returns true for settings that are changed in the user-changes container.
def test_hasUserValueUserChanges(global_stack): def test_hasUserValueUserChanges(global_stack):
container = unittest.mock.MagicMock() 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("infill_sparse_density")
assert not global_stack.hasUserValue("") assert not global_stack.hasUserValue("")
## Tests whether the hasUserValue returns true for settings that are changed in the quality-changes container. ## Tests whether the hasUserValue returns true for settings that are changed in the quality-changes container.
def test_hasUserValueQualityChanges(global_stack): def test_hasUserValueQualityChanges(global_stack):
container = unittest.mock.MagicMock() 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("infill_sparse_density")
assert not global_stack.hasUserValue("") assert not global_stack.hasUserValue("")
## Tests whether a container in some other place on the stack is correctly not recognised as user value. ## Tests whether a container in some other place on the stack is correctly not recognised as user value.
def test_hasNoUserValue(global_stack): def test_hasNoUserValue(global_stack):
container = unittest.mock.MagicMock() 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. 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. ## Tests whether inserting a container is properly forbidden.
def test_insertContainer(global_stack): def test_insertContainer(global_stack):
with pytest.raises(InvalidOperationError): with pytest.raises(InvalidOperationError):
global_stack.insertContainer(0, unittest.mock.MagicMock()) global_stack.insertContainer(0, unittest.mock.MagicMock())
## Tests whether removing a container is properly forbidden. ## Tests whether removing a container is properly forbidden.
def test_removeContainer(global_stack): def test_removeContainer(global_stack):
with pytest.raises(InvalidOperationError): with pytest.raises(InvalidOperationError):
global_stack.removeContainer(unittest.mock.MagicMock()) global_stack.removeContainer(unittest.mock.MagicMock())
## Tests whether changing the next stack is properly forbidden. ## Tests whether changing the next stack is properly forbidden.
def test_setNextStack(global_stack): def test_setNextStack(global_stack):
with pytest.raises(InvalidOperationError): with pytest.raises(InvalidOperationError):
global_stack.setNextStack(unittest.mock.MagicMock()) global_stack.setNextStack(unittest.mock.MagicMock())
## Tests setting properties directly on the global stack. ## Tests setting properties directly on the global stack.
@pytest.mark.parametrize("key, property, value", [ @pytest.mark.parametrize("key, property, value", [
("layer_height", "value", 0.1337), ("layer_height", "value", 0.1337),
@ -415,6 +453,7 @@ def test_setPropertyUser(key, property, value, global_stack):
user_changes.getMetaDataEntry = unittest.mock.MagicMock(return_value = "user") user_changes.getMetaDataEntry = unittest.mock.MagicMock(return_value = "user")
global_stack.userChanges = user_changes global_stack.userChanges = user_changes
global_stack.setProperty(key, property, value) #The actual test. 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)