Merge branch 'master' of github.com:Ultimaker/Cura

This commit is contained in:
Aleksei S 2018-09-28 16:10:50 +02:00
commit cbad60898a
21 changed files with 170 additions and 72 deletions

45
Jenkinsfile vendored
View File

@ -52,12 +52,55 @@ 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
try {
// This also does code style checks.
bat 'ctest -V'
} 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})
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

@ -9,6 +9,7 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Scene.SceneNode import SceneNode
from UM.Scene.Selection import Selection
from UM.i18n import i18nCatalog
from collections import defaultdict
catalog = i18nCatalog("cura")
@ -40,6 +41,8 @@ class ObjectsModel(ListModel):
filter_current_build_plate = Application.getInstance().getPreferences().getValue("view/filter_current_build_plate")
active_build_plate_number = self._build_plate_number
group_nr = 1
name_count_dict = defaultdict(int)
for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()):
if not isinstance(node, SceneNode):
continue
@ -55,6 +58,7 @@ class ObjectsModel(ListModel):
if not node.callDecoration("isGroup"):
name = node.getName()
else:
name = catalog.i18nc("@label", "Group #{group_nr}").format(group_nr = str(group_nr))
group_nr += 1
@ -64,6 +68,14 @@ class ObjectsModel(ListModel):
else:
is_outside_build_area = False
#check if we already have an instance of the object based on name
name_count_dict[name] += 1
name_count = name_count_dict[name]
if name_count > 1:
name = "{0}({1})".format(name, name_count-1)
node.setName(name)
nodes.append({
"name": name,
"isSelected": Selection.isSelected(node),
@ -71,6 +83,7 @@ class ObjectsModel(ListModel):
"buildPlateNumber": node_build_plate_number,
"node": node
})
nodes = sorted(nodes, key=lambda n: n["name"])
self.setItems(nodes)

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

@ -66,7 +66,7 @@ class GenericOutputController(PrinterOutputController):
self._output_device.sendCommand("G28 Z")
def sendRawCommand(self, printer: "PrinterOutputModel", command: str):
self._output_device.sendCommand(command)
self._output_device.sendCommand(command.upper()) #Most printers only understand uppercase g-code commands.
def setJobState(self, job: "PrintJobOutputModel", state: str):
if state == "pause":

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

@ -15,8 +15,6 @@ import numpy
from typing import TYPE_CHECKING, Any, Optional
if TYPE_CHECKING:
from UM.Scene.SceneNode import SceneNode
from cura.Settings.GlobalStack import GlobalStack
@ -35,10 +33,11 @@ class ConvexHullDecorator(SceneNodeDecorator):
# Make sure the timer is created on the main thread
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)
@ -72,14 +71,14 @@ class ConvexHullDecorator(SceneNodeDecorator):
def __deepcopy__(self, memo):
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) -> 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)))
@ -120,6 +119,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
return self._compute2DConvexHull()
return None
## 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()
@ -161,7 +161,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
self._2d_convex_hull_mesh_world_transform = None
self._2d_convex_hull_mesh_result = None
def _compute2DConvexHull(self) -> Polygon:
def _compute2DConvexHull(self) -> Optional[Polygon]:
if self._node.callDecoration("isGroup"):
points = numpy.zeros((0, 2), dtype=numpy.int32)
for child in self._node.getChildren():
@ -188,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()
@ -242,17 +240,22 @@ class ConvexHullDecorator(SceneNodeDecorator):
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._compute2DConvexHull()
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
@ -276,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)
@ -354,7 +357,7 @@ 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.
## 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

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

@ -29,6 +29,7 @@ message Object
bytes normals = 3; //An array of 3 floats.
bytes indices = 4; //An array of ints.
repeated Setting settings = 5; // Setting override per object, overruling the global settings.
string name = 6;
}
message Progress

View File

@ -270,7 +270,7 @@ class StartSliceJob(Job):
obj = group_message.addRepeatedMessage("objects")
obj.id = id(object)
obj.name = object.getName()
indices = mesh_data.getIndices()
if indices is not None:
flat_verts = numpy.take(verts, indices.flatten(), axis=0)

View File

@ -70,7 +70,7 @@ class GCodeWriter(MeshWriter):
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
scene = Application.getInstance().getController().getScene()
if not hasattr(scene, "gcode_dict"):
self.setInformation(catalog.i18nc("@warning:status", "Please generate G-code before saving."))
self.setInformation(catalog.i18nc("@warning:status", "Please prepare G-code before exporting."))
return False
gcode_dict = getattr(scene, "gcode_dict")
gcode_list = gcode_dict.get(active_build_plate, None)
@ -86,7 +86,7 @@ class GCodeWriter(MeshWriter):
stream.write(settings)
return True
self.setInformation(catalog.i18nc("@warning:status", "Please generate G-code before saving."))
self.setInformation(catalog.i18nc("@warning:status", "Please prepare G-code before exporting."))
return False
## Create a new container with container 2 as base and container 1 written over it.

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

@ -75,6 +75,7 @@ Item
Action
{
id:toggleFullScreenAction
shortcut: StandardKey.FullScreen;
text: catalog.i18nc("@action:inmenu","Toggle Full Screen");
iconName: "view-fullscreen";
}

View File

@ -73,4 +73,7 @@ Menu
MenuSeparator {}
MenuItem { action: Cura.Actions.expandSidebar; }
MenuSeparator {}
MenuItem { action: Cura.Actions.toggleFullScreen; }
}

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)