diff --git a/.gitignore b/.gitignore index 52d888f465..527e5dc049 100644 --- a/.gitignore +++ b/.gitignore @@ -35,7 +35,9 @@ plugins/GodMode plugins/PostProcessingPlugin plugins/X3GWriter plugins/FlatProfileExporter +plugins/ProfileFlattener plugins/cura-god-mode-plugin +plugins/cura-big-flame-graph #Build stuff CMakeCache.txt diff --git a/cura.desktop.in b/cura.desktop.in index ceb5c99f08..1f6d295dd2 100644 --- a/cura.desktop.in +++ b/cura.desktop.in @@ -1,5 +1,4 @@ [Desktop Entry] -Version=1 Name=Cura Name[de]=Cura GenericName=3D Printing Software diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index fbf4ba5080..9c22d5ae3a 100755 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -600,20 +600,21 @@ class BuildVolume(SceneNode): result_areas[extruder_id].append(polygon) #Don't perform the offset on these. # Add prime tower location as disallowed area. - prime_tower_collision = False - prime_tower_areas = self._computeDisallowedAreasPrinted(used_extruders) - for extruder_id in prime_tower_areas: - for prime_tower_area in prime_tower_areas[extruder_id]: - for area in result_areas[extruder_id]: - if prime_tower_area.intersectsPolygon(area) is not None: - prime_tower_collision = True + if len(used_extruders) > 1: #No prime tower in single-extrusion. + prime_tower_collision = False + prime_tower_areas = self._computeDisallowedAreasPrinted(used_extruders) + for extruder_id in prime_tower_areas: + for prime_tower_area in prime_tower_areas[extruder_id]: + for area in result_areas[extruder_id]: + if prime_tower_area.intersectsPolygon(area) is not None: + prime_tower_collision = True + break + if prime_tower_collision: #Already found a collision. break - if prime_tower_collision: #Already found a collision. - break - if not prime_tower_collision: - result_areas[extruder_id].extend(prime_tower_areas[extruder_id]) - else: - self._error_areas.extend(prime_tower_areas[extruder_id]) + if not prime_tower_collision: + result_areas[extruder_id].extend(prime_tower_areas[extruder_id]) + else: + self._error_areas.extend(prime_tower_areas[extruder_id]) self._has_errors = len(self._error_areas) > 0 diff --git a/cura/CrashHandler.py b/cura/CrashHandler.py index b658f88824..4048b409a7 100644 --- a/cura/CrashHandler.py +++ b/cura/CrashHandler.py @@ -48,35 +48,32 @@ def show(exception_type, value, tb): dialog = QDialog() dialog.setMinimumWidth(640) dialog.setMinimumHeight(640) - dialog.setWindowTitle(catalog.i18nc("@title:window", "Oops!")) + dialog.setWindowTitle(catalog.i18nc("@title:window", "Crash Report")) layout = QVBoxLayout(dialog) - label = QLabel(dialog) - pixmap = QPixmap() - - try: - data = urllib.request.urlopen("http://www.randomkittengenerator.com/cats/rotator.php").read() - pixmap.loadFromData(data) - except: - try: - from UM.Resources import Resources - path = Resources.getPath(Resources.Images, "kitten.jpg") - pixmap.load(path) - except: - pass - - pixmap = pixmap.scaled(150, 150) - label.setPixmap(pixmap) - label.setAlignment(Qt.AlignCenter) - layout.addWidget(label) + #label = QLabel(dialog) + #pixmap = QPixmap() + #try: + # data = urllib.request.urlopen("http://www.randomkittengenerator.com/cats/rotator.php").read() + # pixmap.loadFromData(data) + #except: + # try: + # from UM.Resources import Resources + # path = Resources.getPath(Resources.Images, "kitten.jpg") + # pixmap.load(path) + # except: + # pass + #pixmap = pixmap.scaled(150, 150) + #label.setPixmap(pixmap) + #label.setAlignment(Qt.AlignCenter) + #layout.addWidget(label) label = QLabel(dialog) layout.addWidget(label) #label.setScaledContents(True) label.setText(catalog.i18nc("@label", """

A fatal exception has occurred that we could not recover from!

-

We hope this picture of a kitten helps you recover from the shock.

Please use the information below to post a bug report at http://github.com/Ultimaker/Cura/issues

""")) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 5786c82147..3b5557b7d8 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -69,6 +69,8 @@ from cura.Settings.ContainerSettingsModel import ContainerSettingsModel from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler from cura.Settings.QualitySettingsModel import QualitySettingsModel from cura.Settings.ContainerManager import ContainerManager +from cura.Settings.GlobalStack import GlobalStack +from cura.Settings.ExtruderStack import ExtruderStack from PyQt5.QtCore import QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS from UM.FlameProfiler import pyqtSlot @@ -106,6 +108,7 @@ class CuraApplication(QtApplication): UserInstanceContainer = Resources.UserType + 6 MachineStack = Resources.UserType + 7 ExtruderStack = Resources.UserType + 8 + DefinitionChangesContainer = Resources.UserType + 9 Q_ENUMS(ResourceTypes) @@ -151,6 +154,7 @@ class CuraApplication(QtApplication): Resources.addStorageType(self.ResourceTypes.UserInstanceContainer, "user") Resources.addStorageType(self.ResourceTypes.ExtruderStack, "extruders") Resources.addStorageType(self.ResourceTypes.MachineStack, "machine_instances") + Resources.addStorageType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes") ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer) ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.VariantInstanceContainer) @@ -158,6 +162,7 @@ class CuraApplication(QtApplication): ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.UserInstanceContainer) ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.ExtruderStack) ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.MachineStack) + ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.DefinitionChangesContainer) ## Initialise the version upgrade manager with Cura's storage paths. import UM.VersionUpgradeManager #Needs to be here to prevent circular dependencies. @@ -326,6 +331,7 @@ class CuraApplication(QtApplication): blackmagic print_sequence infill_mesh + cutting_mesh experimental """.replace("\n", ";").replace(" ", "")) @@ -413,7 +419,7 @@ class CuraApplication(QtApplication): elif instance_type == "variant": path = Resources.getStoragePath(self.ResourceTypes.VariantInstanceContainer, file_name) elif instance_type == "definition_changes": - path = Resources.getStoragePath(self.ResourceTypes.MachineStack, file_name) + path = Resources.getStoragePath(self.ResourceTypes.DefinitionChangesContainer, file_name) if path: instance.setPath(path) @@ -436,16 +442,18 @@ class CuraApplication(QtApplication): mime_type = ContainerRegistry.getMimeTypeForContainer(type(stack)) file_name = urllib.parse.quote_plus(stack.getId()) + "." + mime_type.preferredSuffix - stack_type = stack.getMetaDataEntry("type", None) + path = None - if not stack_type or stack_type == "machine": + if isinstance(stack, GlobalStack): path = Resources.getStoragePath(self.ResourceTypes.MachineStack, file_name) - elif stack_type == "extruder_train": + elif isinstance(stack, ExtruderStack): path = Resources.getStoragePath(self.ResourceTypes.ExtruderStack, file_name) - if path: - stack.setPath(path) - with SaveFile(path, "wt") as f: - f.write(data) + else: + path = Resources.getStoragePath(Resources.ContainerStacks, file_name) + + stack.setPath(path) + with SaveFile(path, "wt") as f: + f.write(data) @pyqtSlot(str, result = QUrl) @@ -1274,6 +1282,8 @@ class CuraApplication(QtApplication): arranger = Arrange.create(scene_root = root) min_offset = 8 + self.fileLoaded.emit(filename) + for node in nodes: node.setSelectable(True) node.setName(os.path.basename(filename)) diff --git a/cura/PrintInformation.py b/cura/PrintInformation.py index 1eb7aaa7dd..c6412e2f6f 100644 --- a/cura/PrintInformation.py +++ b/cura/PrintInformation.py @@ -52,6 +52,19 @@ class PrintInformation(QObject): super().__init__(parent) self._current_print_time = Duration(None, self) + self._print_times_per_feature = { + "none": Duration(None, self), + "inset_0": Duration(None, self), + "inset_x": Duration(None, self), + "skin": Duration(None, self), + "support": Duration(None, self), + "skirt": Duration(None, self), + "infill": Duration(None, self), + "support_infill": Duration(None, self), + "travel": Duration(None, self), + "retract": Duration(None, self), + "support_interface": Duration(None, self) + } self._material_lengths = [] self._material_weights = [] @@ -93,6 +106,10 @@ class PrintInformation(QObject): def currentPrintTime(self): return self._current_print_time + @pyqtProperty("QVariantMap", notify = currentPrintTimeChanged) + def printTimesPerFeature(self): + return self._print_times_per_feature + materialLengthsChanged = pyqtSignal() @pyqtProperty("QVariantList", notify = materialLengthsChanged) @@ -111,12 +128,16 @@ class PrintInformation(QObject): def materialCosts(self): return self._material_costs - def _onPrintDurationMessage(self, total_time, material_amounts): - if total_time != total_time: # Check for NaN. Engine can sometimes give us weird values. - Logger.log("w", "Received NaN for print duration message") - self._current_print_time.setDuration(0) - else: - self._current_print_time.setDuration(total_time) + def _onPrintDurationMessage(self, time_per_feature, material_amounts): + total_time = 0 + for feature, time in time_per_feature.items(): + if time != time: # Check for NaN. Engine can sometimes give us weird values. + self._print_times_per_feature[feature].setDuration(0) + Logger.log("w", "Received NaN for print duration message") + continue + total_time += time + self._print_times_per_feature[feature].setDuration(time) + self._current_print_time.setDuration(total_time) self.currentPrintTimeChanged.emit() @@ -183,7 +204,10 @@ class PrintInformation(QObject): def _onActiveMaterialChanged(self): if self._active_material_container: - self._active_material_container.metaDataChanged.disconnect(self._onMaterialMetaDataChanged) + try: + self._active_material_container.metaDataChanged.disconnect(self._onMaterialMetaDataChanged) + except TypeError: #pyQtSignal gives a TypeError when disconnecting from something that is already disconnected. + pass active_material_id = Application.getInstance().getMachineManager().activeMaterialId active_material_containers = ContainerRegistry.getInstance().findInstanceContainers(id=active_material_id) diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index 4c408806b9..ff83134ce8 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -430,7 +430,7 @@ class ContainerManager(QObject): for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks(): # Find the quality_changes container for this stack and merge the contents of the top container into it. - quality_changes = stack.findContainer(type = "quality_changes") + quality_changes = stack.qualityChanges if not quality_changes or quality_changes.isReadOnly(): Logger.log("e", "Could not update quality of a nonexistant or read only quality profile in stack %s", stack.getId()) continue @@ -483,8 +483,8 @@ class ContainerManager(QObject): # Go through the active stacks and create quality_changes containers from the user containers. for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks(): user_container = stack.getTop() - quality_container = stack.findContainer(type = "quality") - quality_changes_container = stack.findContainer(type = "quality_changes") + quality_container = stack.quality + quality_changes_container = stack.qualityChanges if not quality_container or not quality_changes_container: Logger.log("w", "No quality or quality changes container found in stack %s, ignoring it", stack.getId()) continue @@ -605,7 +605,7 @@ class ContainerManager(QObject): machine_definition = global_stack.getBottom() active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() - material_containers = [stack.findContainer(type="material") for stack in active_stacks] + material_containers = [stack.material for stack in active_stacks] result = self._duplicateQualityOrQualityChangesForMachineType(quality_name, base_name, QualityManager.getInstance().getParentMachineDefinition(machine_definition), diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index 3982418070..72b94a6f8d 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -6,6 +6,7 @@ import os.path import re from PyQt5.QtWidgets import QMessageBox +from UM.Decorators import override from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerStack import ContainerStack from UM.Settings.InstanceContainer import InstanceContainer @@ -16,8 +17,10 @@ from UM.Platform import Platform from UM.PluginRegistry import PluginRegistry #For getting the possible profile writers to write with. from UM.Util import parseBool -from cura.Settings.ExtruderManager import ExtruderManager -from cura.Settings.ContainerManager import ContainerManager +from . import ExtruderStack +from . import GlobalStack +from .ContainerManager import ContainerManager +from .ExtruderManager import ExtruderManager from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") @@ -26,6 +29,20 @@ class CuraContainerRegistry(ContainerRegistry): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + ## Overridden from ContainerRegistry + # + # Adds a container to the registry. + # + # This will also try to convert a ContainerStack to either Extruder or + # Global stack based on metadata information. + @override(ContainerRegistry) + def addContainer(self, container): + # Note: Intentional check with type() because we want to ignore subclasses + if type(container) == ContainerStack: + container = self._convertContainerStack(container) + + super().addContainer(container) + ## Create a name that is not empty and unique # \param container_type \type{string} Type of the container (machine, quality, ...) # \param current_name \type{} Current name of the container, which may be an acceptable option @@ -212,6 +229,11 @@ class CuraContainerRegistry(ContainerRegistry): # If it hasn't returned by now, none of the plugins loaded the profile successfully. return {"status": "error", "message": catalog.i18nc("@info:status", "Profile {0} has an unknown file type or is corrupted.", file_name)} + @override(ContainerRegistry) + def load(self): + super().load() + self._fixupExtruders() + def _configureProfile(self, profile, id_seed, new_name): profile.setReadOnly(False) profile.setDirty(True) # Ensure the profiles are correctly saved @@ -284,3 +306,47 @@ class CuraContainerRegistry(ContainerRegistry): if global_container_stack: return parseBool(global_container_stack.getMetaDataEntry("has_machine_quality", False)) return False + + ## Convert an "old-style" pure ContainerStack to either an Extruder or Global stack. + def _convertContainerStack(self, container): + assert type(container) == ContainerStack + + container_type = container.getMetaDataEntry("type") + if container_type not in ("extruder_train", "machine"): + # It is not an extruder or machine, so do nothing with the stack + return container + + Logger.log("d", "Converting ContainerStack {stack} to {type}", stack = container.getId(), type = container_type) + + new_stack = None + if container_type == "extruder_train": + new_stack = ExtruderStack.ExtruderStack(container.getId()) + else: + new_stack = GlobalStack.GlobalStack(container.getId()) + + container_contents = container.serialize() + new_stack.deserialize(container_contents) + + # Delete the old configuration file so we do not get double stacks + if os.path.isfile(container.getPath()): + os.remove(container.getPath()) + + return new_stack + + # Fix the extruders that were upgraded to ExtruderStack instances during addContainer. + # The stacks are now responsible for setting the next stack on deserialize. However, + # due to problems with loading order, some stacks may not have the proper next stack + # set after upgrading, because the proper global stack was not yet loaded. This method + # makes sure those extruders also get the right stack set. + def _fixupExtruders(self): + extruder_stacks = self.findContainers(ExtruderStack.ExtruderStack) + for extruder_stack in extruder_stacks: + if extruder_stack.getNextStack(): + # Has the right next stack, so ignore it. + continue + + machines = ContainerRegistry.getInstance().findContainerStacks(id=extruder_stack.getMetaDataEntry("machine", "")) + if machines: + extruder_stack.setNextStack(machines[0]) + else: + Logger.log("w", "Could not find machine {machine} for extruder {extruder}", machine = extruder_stack.getMetaDataEntry("machine"), extruder = extruder_stack.getId()) diff --git a/cura/Settings/CuraContainerStack.py b/cura/Settings/CuraContainerStack.py new file mode 100755 index 0000000000..6cffa6030b --- /dev/null +++ b/cura/Settings/CuraContainerStack.py @@ -0,0 +1,608 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + +import os.path + +from typing import Any, Optional + +from PyQt5.QtCore import pyqtProperty, pyqtSignal +from UM.FlameProfiler import pyqtSlot + +from UM.Decorators import override +from UM.Logger import Logger +from UM.Settings.ContainerStack import ContainerStack, InvalidContainerStackError +from UM.Settings.InstanceContainer import InstanceContainer +from UM.Settings.DefinitionContainer import DefinitionContainer +from UM.Settings.ContainerRegistry import ContainerRegistry +from UM.Settings.Interfaces import ContainerInterface + +from . import Exceptions + + +## Base class for Cura related stacks that want to enforce certain containers are available. +# +# This class makes sure that the stack has the following containers set: user changes, quality +# changes, quality, material, variant, definition changes and finally definition. Initially, +# these will be equal to the empty instance container. +# +# The container types are determined based on the following criteria: +# - user: An InstanceContainer with the metadata entry "type" set to "user". +# - quality changes: An InstanceContainer with the metadata entry "type" set to "quality_changes". +# - quality: An InstanceContainer with the metadata entry "type" set to "quality". +# - material: An InstanceContainer with the metadata entry "type" set to "material". +# - variant: An InstanceContainer with the metadata entry "type" set to "variant". +# - definition changes: An InstanceContainer with the metadata entry "type" set to "definition_changes". +# - definition: A DefinitionContainer. +# +# Internally, this class ensures the mentioned containers are always there and kept in a specific order. +# This also means that operations on the stack that modifies the container ordering is prohibited and +# will raise an exception. +class CuraContainerStack(ContainerStack): + def __init__(self, container_id: str, *args, **kwargs): + super().__init__(container_id, *args, **kwargs) + + self._empty_instance_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() + + self._containers = [self._empty_instance_container for i in range(len(_ContainerIndexes.IndexTypeMap))] + + self.containersChanged.connect(self._onContainersChanged) + + # This is emitted whenever the containersChanged signal from the ContainerStack base class is emitted. + pyqtContainersChanged = pyqtSignal() + + ## Set the user changes container. + # + # \param new_user_changes The new user changes container. It is expected to have a "type" metadata entry with the value "user". + def setUserChanges(self, new_user_changes: InstanceContainer) -> None: + self.replaceContainer(_ContainerIndexes.UserChanges, new_user_changes) + + ## Get the user changes container. + # + # \return The user changes container. Should always be a valid container, but can be equal to the empty InstanceContainer. + @pyqtProperty(InstanceContainer, fset = setUserChanges, notify = pyqtContainersChanged) + def userChanges(self) -> InstanceContainer: + return self._containers[_ContainerIndexes.UserChanges] + + ## Set the quality changes container. + # + # \param new_quality_changes The new quality changes container. It is expected to have a "type" metadata entry with the value "quality_changes". + def setQualityChanges(self, new_quality_changes: InstanceContainer, postpone_emit = False) -> None: + self.replaceContainer(_ContainerIndexes.QualityChanges, new_quality_changes, postpone_emit = postpone_emit) + + ## Set the quality changes container by an ID. + # + # This will search for the specified container and set it. If no container was found, an error will be raised. + # + # \param new_quality_changes_id The ID of the new quality changes container. + # + # \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID. + def setQualityChangesById(self, new_quality_changes_id: str) -> None: + quality_changes = ContainerRegistry.getInstance().findInstanceContainers(id = new_quality_changes_id) + if quality_changes: + self.setQualityChanges(quality_changes[0]) + else: + raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_quality_changes_id)) + + ## Get the quality changes container. + # + # \return The quality changes container. Should always be a valid container, but can be equal to the empty InstanceContainer. + @pyqtProperty(InstanceContainer, fset = setQualityChanges, notify = pyqtContainersChanged) + def qualityChanges(self) -> InstanceContainer: + return self._containers[_ContainerIndexes.QualityChanges] + + ## Set the quality container. + # + # \param new_quality The new quality container. It is expected to have a "type" metadata entry with the value "quality". + def setQuality(self, new_quality: InstanceContainer, postpone_emit = False) -> None: + self.replaceContainer(_ContainerIndexes.Quality, new_quality, postpone_emit = postpone_emit) + + ## Set the quality container by an ID. + # + # This will search for the specified container and set it. If no container was found, an error will be raised. + # There is a special value for ID, which is "default". The "default" value indicates the quality should be set + # to whatever the machine definition specifies as "preferred" container, or a fallback value. See findDefaultQuality + # for details. + # + # \param new_quality_id The ID of the new quality container. + # + # \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID. + def setQualityById(self, new_quality_id: str) -> None: + quality = self._empty_instance_container + if new_quality_id == "default": + new_quality = self.findDefaultQuality() + if new_quality: + quality = new_quality + else: + qualities = ContainerRegistry.getInstance().findInstanceContainers(id = new_quality_id) + if qualities: + quality = qualities[0] + else: + raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_quality_id)) + + self.setQuality(quality) + + ## Get the quality container. + # + # \return The quality container. Should always be a valid container, but can be equal to the empty InstanceContainer. + @pyqtProperty(InstanceContainer, fset = setQuality, notify = pyqtContainersChanged) + def quality(self) -> InstanceContainer: + return self._containers[_ContainerIndexes.Quality] + + ## Set the material container. + # + # \param new_quality_changes The new material container. It is expected to have a "type" metadata entry with the value "quality_changes". + def setMaterial(self, new_material: InstanceContainer, postpone_emit = False) -> None: + self.replaceContainer(_ContainerIndexes.Material, new_material, postpone_emit = postpone_emit) + + ## Set the material container by an ID. + # + # This will search for the specified container and set it. If no container was found, an error will be raised. + # There is a special value for ID, which is "default". The "default" value indicates the quality should be set + # to whatever the machine definition specifies as "preferred" container, or a fallback value. See findDefaultMaterial + # for details. + # + # \param new_quality_changes_id The ID of the new material container. + # + # \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID. + def setMaterialById(self, new_material_id: str) -> None: + material = self._empty_instance_container + if new_material_id == "default": + new_material = self.findDefaultMaterial() + if new_material: + material = new_material + else: + materials = ContainerRegistry.getInstance().findInstanceContainers(id = new_material_id) + if materials: + material = materials[0] + else: + raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_material_id)) + + self.setMaterial(material) + + ## Get the material container. + # + # \return The material container. Should always be a valid container, but can be equal to the empty InstanceContainer. + @pyqtProperty(InstanceContainer, fset = setMaterial, notify = pyqtContainersChanged) + def material(self) -> InstanceContainer: + return self._containers[_ContainerIndexes.Material] + + ## Set the variant container. + # + # \param new_quality_changes The new variant container. It is expected to have a "type" metadata entry with the value "quality_changes". + def setVariant(self, new_variant: InstanceContainer) -> None: + self.replaceContainer(_ContainerIndexes.Variant, new_variant) + + ## Set the variant container by an ID. + # + # This will search for the specified container and set it. If no container was found, an error will be raised. + # There is a special value for ID, which is "default". The "default" value indicates the quality should be set + # to whatever the machine definition specifies as "preferred" container, or a fallback value. See findDefaultVariant + # for details. + # + # \param new_quality_changes_id The ID of the new variant container. + # + # \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID. + def setVariantById(self, new_variant_id: str) -> None: + variant = self._empty_instance_container + if new_variant_id == "default": + new_variant = self.findDefaultVariant() + if new_variant: + variant = new_variant + else: + variants = ContainerRegistry.getInstance().findInstanceContainers(id = new_variant_id) + if variants: + variant = variants[0] + else: + raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_variant_id)) + + self.setVariant(variant) + + ## Get the variant container. + # + # \return The variant container. Should always be a valid container, but can be equal to the empty InstanceContainer. + @pyqtProperty(InstanceContainer, fset = setVariant, notify = pyqtContainersChanged) + def variant(self) -> InstanceContainer: + return self._containers[_ContainerIndexes.Variant] + + ## Set the definition changes container. + # + # \param new_quality_changes The new definition changes container. It is expected to have a "type" metadata entry with the value "quality_changes". + def setDefinitionChanges(self, new_definition_changes: InstanceContainer) -> None: + self.replaceContainer(_ContainerIndexes.DefinitionChanges, new_definition_changes) + + ## Set the definition changes container by an ID. + # + # \param new_quality_changes_id The ID of the new definition changes container. + # + # \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID. + def setDefinitionChangesById(self, new_definition_changes_id: str) -> None: + new_definition_changes = ContainerRegistry.getInstance().findInstanceContainers(id = new_definition_changes_id) + if new_definition_changes: + self.setDefinitionChanges(new_definition_changes[0]) + else: + raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_definition_changes_id)) + + ## Get the definition changes container. + # + # \return The definition changes container. Should always be a valid container, but can be equal to the empty InstanceContainer. + @pyqtProperty(InstanceContainer, fset = setDefinitionChanges, notify = pyqtContainersChanged) + def definitionChanges(self) -> InstanceContainer: + return self._containers[_ContainerIndexes.DefinitionChanges] + + ## Set the definition container. + # + # \param new_quality_changes The new definition container. It is expected to have a "type" metadata entry with the value "quality_changes". + def setDefinition(self, new_definition: DefinitionContainer) -> None: + self.replaceContainer(_ContainerIndexes.Definition, new_definition) + + ## Set the definition container by an ID. + # + # \param new_quality_changes_id The ID of the new definition container. + # + # \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID. + def setDefinitionById(self, new_definition_id: str) -> None: + new_definition = ContainerRegistry.getInstance().findDefinitionContainers(id = new_definition_id) + if new_definition: + self.setDefinition(new_definition[0]) + else: + raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_definition_id)) + + ## Get the definition container. + # + # \return The definition container. Should always be a valid container, but can be equal to the empty InstanceContainer. + @pyqtProperty(DefinitionContainer, fset = setDefinition, notify = pyqtContainersChanged) + def definition(self) -> DefinitionContainer: + return self._containers[_ContainerIndexes.Definition] + + ## Check whether the specified setting has a 'user' value. + # + # A user value here is defined as the setting having a value in either + # the UserChanges or QualityChanges container. + # + # \return True if the setting has a user value, False if not. + @pyqtSlot(str, result = bool) + def hasUserValue(self, key: str) -> bool: + if self._containers[_ContainerIndexes.UserChanges].hasProperty(key, "value"): + return True + + if self._containers[_ContainerIndexes.QualityChanges].hasProperty(key, "value"): + return True + + return False + + ## Set a property of a setting. + # + # This will set a property of a specified setting. Since the container stack does not contain + # any settings itself, it is required to specify a container to set the property on. The target + # container is matched by container type. + # + # \param key The key of the setting to set. + # \param property_name The name of the property to set. + # \param new_value The new value to set the property to. + # \param target_container The type of the container to set the property of. Defaults to "user". + def setProperty(self, key: str, property_name: str, new_value: Any, target_container: str = "user") -> None: + container_index = _ContainerIndexes.TypeIndexMap.get(target_container, -1) + if container_index != -1: + self._containers[container_index].setProperty(key, property_name, new_value) + else: + raise IndexError("Invalid target container {type}".format(type = target_container)) + + ## Overridden from ContainerStack + # + # Since we have a fixed order of containers in the stack and this method would modify the container + # ordering, we disallow this operation. + @override(ContainerStack) + def addContainer(self, container: ContainerInterface) -> None: + raise Exceptions.InvalidOperationError("Cannot add a container to Global stack") + + ## Overridden from ContainerStack + # + # Since we have a fixed order of containers in the stack and this method would modify the container + # ordering, we disallow this operation. + @override(ContainerStack) + def insertContainer(self, index: int, container: ContainerInterface) -> None: + raise Exceptions.InvalidOperationError("Cannot insert a container into Global stack") + + ## Overridden from ContainerStack + # + # Since we have a fixed order of containers in the stack and this method would modify the container + # ordering, we disallow this operation. + @override(ContainerStack) + def removeContainer(self, index: int = 0) -> None: + raise Exceptions.InvalidOperationError("Cannot remove a container from Global stack") + + ## Overridden from ContainerStack + # + # Replaces the container at the specified index with another container. + # This version performs checks to make sure the new container has the expected metadata and type. + # + # \throws Exception.InvalidContainerError Raised when trying to replace a container with a container that has an incorrect type. + @override(ContainerStack) + def replaceContainer(self, index: int, container: ContainerInterface, postpone_emit: bool = False) -> None: + expected_type = _ContainerIndexes.IndexTypeMap[index] + if expected_type == "definition": + if not isinstance(container, DefinitionContainer): + raise Exceptions.InvalidContainerError("Cannot replace container at index {index} with a container that is not a DefinitionContainer".format(index = index)) + elif container != self._empty_instance_container and container.getMetaDataEntry("type") != expected_type: + raise Exceptions.InvalidContainerError("Cannot replace container at index {index} with a container that is not of {type} type, but {actual_type} type.".format(index = index, type = expected_type, actual_type = container.getMetaDataEntry("type"))) + + super().replaceContainer(index, container, postpone_emit) + + ## Overridden from ContainerStack + # + # This deserialize will make sure the internal list of containers matches with what we expect. + # It will first check to see if the container at a certain index already matches with what we + # expect. If it does not, it will search for a matching container with the correct type. Should + # no container with the correct type be found, it will use the empty container. + # + # \throws InvalidContainerStackError Raised when no definition can be found for the stack. + @override(ContainerStack) + def deserialize(self, contents: str) -> None: + super().deserialize(contents) + + new_containers = self._containers.copy() + while len(new_containers) < len(_ContainerIndexes.IndexTypeMap): + new_containers.append(self._empty_instance_container) + + # Validate and ensure the list of containers matches with what we expect + for index, type_name in _ContainerIndexes.IndexTypeMap.items(): + try: + container = new_containers[index] + except IndexError: + container = None + + if type_name == "definition": + if not container or not isinstance(container, DefinitionContainer): + definition = self.findContainer(container_type = DefinitionContainer) + if not definition: + raise InvalidContainerStackError("Stack {id} does not have a definition!".format(id = self._id)) + + new_containers[index] = definition + continue + + if not container or container.getMetaDataEntry("type") != type_name: + actual_container = self.findContainer(type = type_name) + if actual_container: + new_containers[index] = actual_container + else: + new_containers[index] = self._empty_instance_container + + self._containers = new_containers + + ## Find the variant that should be used as "default" variant. + # + # This will search for variants that match the current definition and pick the preferred one, + # if specified by the machine definition. + # + # The following criteria are used to find the default variant: + # - If the machine definition does not have a metadata entry "has_variants" set to True, return None + # - The definition of the variant should be the same as the machine definition for this stack. + # - The container should have a metadata entry "type" with value "variant". + # - If the machine definition has a metadata entry "preferred_variant", filter the variant IDs based on that. + # + # \return The container that should be used as default, or None if nothing was found or the machine does not use variants. + # + # \note This method assumes the stack has a valid machine definition. + def findDefaultVariant(self) -> Optional[ContainerInterface]: + definition = self._getMachineDefinition() + if not definition.getMetaDataEntry("has_variants"): + # If the machine does not use variants, we should never set a variant. + return None + + # First add any variant. Later, overwrite with preference if the preference is valid. + variant = None + definition_id = self._findInstanceContainerDefinitionId(definition) + variants = ContainerRegistry.getInstance().findInstanceContainers(definition = definition_id, type = "variant") + if variants: + variant = variants[0] + + preferred_variant_id = definition.getMetaDataEntry("preferred_variant") + if preferred_variant_id: + preferred_variants = ContainerRegistry.getInstance().findInstanceContainers(id = preferred_variant_id, definition = definition_id, type = "variant") + if preferred_variants: + variant = preferred_variants[0] + else: + Logger.log("w", "The preferred variant \"{variant}\" of stack {stack} does not exist or is not a variant.", variant = preferred_variant_id, stack = self.id) + # And leave it at the default variant. + + if variant: + return variant + + Logger.log("w", "Could not find a valid default variant for stack {stack}", stack = self.id) + return None + + ## Find the material that should be used as "default" material. + # + # This will search for materials that match the current definition and pick the preferred one, + # if specified by the machine definition. + # + # The following criteria are used to find the default material: + # - If the machine definition does not have a metadata entry "has_materials" set to True, return None + # - If the machine definition has a metadata entry "has_machine_materials", the definition of the material should + # be the same as the machine definition for this stack. Otherwise, the definition should be "fdmprinter". + # - The container should have a metadata entry "type" with value "material". + # - If the machine definition has a metadata entry "has_variants" and set to True, the "variant" metadata entry of + # the material should be the same as the ID of the variant in the stack. Only applies if "has_machine_materials" is also True. + # - If the stack currently has a material set, try to find a material that matches the current material by name. + # - Otherwise, if the machine definition has a metadata entry "preferred_material", try to find a material that matches the specified ID. + # + # \return The container that should be used as default, or None if nothing was found or the machine does not use materials. + def findDefaultMaterial(self) -> Optional[ContainerInterface]: + definition = self._getMachineDefinition() + if not definition.getMetaDataEntry("has_materials"): + # Machine does not use materials, never try to set it. + return None + + search_criteria = {"type": "material"} + if definition.getMetaDataEntry("has_machine_materials"): + search_criteria["definition"] = self._findInstanceContainerDefinitionId(definition) + + if definition.getMetaDataEntry("has_variants"): + search_criteria["variant"] = self.variant.id + else: + search_criteria["definition"] = "fdmprinter" + + if self.material != self._empty_instance_container: + search_criteria["name"] = self.material.name + else: + preferred_material = definition.getMetaDataEntry("preferred_material") + if preferred_material: + search_criteria["id"] = preferred_material + + materials = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria) + if not materials: + Logger.log("w", "The preferred material \"{material}\" could not be found for stack {stack}", material = preferred_material, stack = self.id) + # We failed to find any materials matching the specified criteria, drop some specific criteria and try to find + # a material that sort-of matches what we want. + search_criteria.pop("variant", None) + search_criteria.pop("id", None) + search_criteria.pop("name", None) + materials = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria) + + if materials: + return materials[0] + + Logger.log("w", "Could not find a valid material for stack {stack}", stack = self.id) + return None + + ## Find the quality that should be used as "default" quality. + # + # This will search for qualities that match the current definition and pick the preferred one, + # if specified by the machine definition. + # + # \return The container that should be used as default, or None if nothing was found. + def findDefaultQuality(self) -> Optional[ContainerInterface]: + definition = self._getMachineDefinition() + registry = ContainerRegistry.getInstance() + material_container = self.material if self.material != self._empty_instance_container else None + + search_criteria = {"type": "quality"} + + if definition.getMetaDataEntry("has_machine_quality"): + search_criteria["definition"] = self._findInstanceContainerDefinitionId(definition) + + if definition.getMetaDataEntry("has_materials") and material_container: + search_criteria["material"] = material_container.id + else: + search_criteria["definition"] = "fdmprinter" + + if self.quality != self._empty_instance_container: + search_criteria["name"] = self.quality.name + else: + preferred_quality = definition.getMetaDataEntry("preferred_quality") + if preferred_quality: + search_criteria["id"] = preferred_quality + + containers = registry.findInstanceContainers(**search_criteria) + if containers: + return containers[0] + + if "material" in search_criteria: + # First check if we can solve our material not found problem by checking if we can find quality containers + # that are assigned to the parents of this material profile. + try: + inherited_files = material_container.getInheritedFiles() + except AttributeError: # Material_container does not support inheritance. + inherited_files = [] + + if inherited_files: + for inherited_file in inherited_files: + # Extract the ID from the path we used to load the file. + search_criteria["material"] = os.path.basename(inherited_file).split(".")[0] + containers = registry.findInstanceContainers(**search_criteria) + if containers: + return containers[0] + + # We still weren't able to find a quality for this specific material. + # Try to find qualities for a generic version of the material. + material_search_criteria = {"type": "material", "material": material_container.getMetaDataEntry("material"), "color_name": "Generic"} + if definition.getMetaDataEntry("has_machine_quality"): + if self.material != self._empty_instance_container: + material_search_criteria["definition"] = material_container.getDefinition().id + + if definition.getMetaDataEntry("has_variants"): + material_search_criteria["variant"] = material_container.getMetaDataEntry("variant") + else: + material_search_criteria["definition"] = self._findInstanceContainerDefinitionId(definition) + + if definition.getMetaDataEntry("has_variants") and self.variant != self._empty_instance_container: + material_search_criteria["variant"] = self.variant.id + else: + material_search_criteria["definition"] = "fdmprinter" + material_containers = registry.findInstanceContainers(**material_search_criteria) + # Try all materials to see if there is a quality profile available. + for material_container in material_containers: + search_criteria["material"] = material_container.getId() + + containers = registry.findInstanceContainers(**search_criteria) + if containers: + return containers[0] + + if "name" in search_criteria or "id" in search_criteria: + # If a quality by this name can not be found, try a wider set of search criteria + search_criteria.pop("name", None) + search_criteria.pop("id", None) + + containers = registry.findInstanceContainers(**search_criteria) + if containers: + return containers[0] + + return None + + ## protected: + + # Helper to make sure we emit a PyQt signal on container changes. + def _onContainersChanged(self, container: Any) -> None: + self.pyqtContainersChanged.emit() + + # Helper that can be overridden to get the "machine" definition, that is, the definition that defines the machine + # and its properties rather than, for example, the extruder. Defaults to simply returning the definition property. + def _getMachineDefinition(self) -> DefinitionContainer: + return self.definition + + ## Find the ID that should be used when searching for instance containers for a specified definition. + # + # This handles the situation where the definition specifies we should use a different definition when + # searching for instance containers. + # + # \param machine_definition The definition to find the "quality definition" for. + # + # \return The ID of the definition container to use when searching for instance containers. + @classmethod + def _findInstanceContainerDefinitionId(cls, machine_definition: DefinitionContainer) -> str: + quality_definition = machine_definition.getMetaDataEntry("quality_definition") + if not quality_definition: + return machine_definition.id + + definitions = ContainerRegistry.getInstance().findDefinitionContainers(id = quality_definition) + if not definitions: + Logger.log("w", "Unable to find parent definition {parent} for machine {machine}", parent = quality_definition, machine = machine_definition.id) + return machine_definition.id + + return cls._findInstanceContainerDefinitionId(definitions[0]) + +## private: + +# Private helper class to keep track of container positions and their types. +class _ContainerIndexes: + UserChanges = 0 + QualityChanges = 1 + Quality = 2 + Material = 3 + Variant = 4 + DefinitionChanges = 5 + Definition = 6 + + # Simple hash map to map from index to "type" metadata entry + IndexTypeMap = { + UserChanges: "user", + QualityChanges: "quality_changes", + Quality: "quality", + Material: "material", + Variant: "variant", + DefinitionChanges: "definition_changes", + Definition: "definition", + } + + # Reverse lookup: type -> index + TypeIndexMap = dict([(v, k) for k, v in IndexTypeMap.items()]) diff --git a/cura/Settings/CuraStackBuilder.py b/cura/Settings/CuraStackBuilder.py new file mode 100644 index 0000000000..a85bae76af --- /dev/null +++ b/cura/Settings/CuraStackBuilder.py @@ -0,0 +1,152 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + +from UM.Logger import Logger + +from UM.Settings.DefinitionContainer import DefinitionContainer +from UM.Settings.InstanceContainer import InstanceContainer +from UM.Settings.ContainerRegistry import ContainerRegistry + +from .GlobalStack import GlobalStack +from .ExtruderStack import ExtruderStack +from .CuraContainerStack import CuraContainerStack +from typing import Optional + + +## Contains helper functions to create new machines. +class CuraStackBuilder: + ## Create a new instance of a machine. + # + # \param name The name of the new machine. + # \param definition_id The ID of the machine definition to use. + # + # \return The new global stack or None if an error occurred. + @classmethod + def createMachine(cls, name: str, definition_id: str) -> Optional[GlobalStack]: + registry = ContainerRegistry.getInstance() + definitions = registry.findDefinitionContainers(id = definition_id) + if not definitions: + Logger.log("w", "Definition {definition} was not found!", definition = definition_id) + return None + + machine_definition = definitions[0] + name = registry.createUniqueName("machine", "", name, machine_definition.name) + + new_global_stack = cls.createGlobalStack( + new_stack_id = name, + definition = machine_definition, + quality = "default", + material = "default", + variant = "default", + ) + + for extruder_definition in registry.findDefinitionContainers(machine = machine_definition.id): + position = extruder_definition.getMetaDataEntry("position", None) + if not position: + Logger.log("w", "Extruder definition %s specifies no position metadata entry.", extruder_definition.id) + + new_extruder_id = registry.uniqueName(extruder_definition.id) + new_extruder = cls.createExtruderStack( + new_extruder_id, + definition = extruder_definition, + machine_definition = machine_definition, + quality = "default", + material = "default", + variant = "default", + next_stack = new_global_stack + ) + + return new_global_stack + + ## Create a new Extruder stack + # + # \param new_stack_id The ID of the new stack. + # \param definition The definition to base the new stack on. + # \param machine_definition The machine definition to use for the user container. + # \param kwargs You can add keyword arguments to specify IDs of containers to use for a specific type, for example "variant": "0.4mm" + # + # \return A new Global stack instance with the specified parameters. + @classmethod + def createExtruderStack(cls, new_stack_id: str, definition: DefinitionContainer, machine_definition: DefinitionContainer, **kwargs) -> ExtruderStack: + stack = ExtruderStack(new_stack_id) + stack.setName(definition.getName()) + stack.setDefinition(definition) + stack.addMetaDataEntry("position", definition.getMetaDataEntry("position")) + + user_container = InstanceContainer(new_stack_id + "_user") + user_container.addMetaDataEntry("type", "user") + user_container.addMetaDataEntry("extruder", new_stack_id) + user_container.setDefinition(machine_definition) + + stack.setUserChanges(user_container) + + if "next_stack" in kwargs: + stack.setNextStack(kwargs["next_stack"]) + + # Important! The order here matters, because that allows the stack to + # assume the material and variant have already been set. + if "definition_changes" in kwargs: + stack.setDefinitionChangesById(kwargs["definition_changes"]) + + if "variant" in kwargs: + stack.setVariantById(kwargs["variant"]) + + if "material" in kwargs: + stack.setMaterialById(kwargs["material"]) + + if "quality" in kwargs: + stack.setQualityById(kwargs["quality"]) + + if "quality_changes" in kwargs: + stack.setQualityChangesById(kwargs["quality_changes"]) + + # Only add the created containers to the registry after we have set all the other + # properties. This makes the create operation more transactional, since any problems + # setting properties will not result in incomplete containers being added. + registry = ContainerRegistry.getInstance() + registry.addContainer(stack) + registry.addContainer(user_container) + + return stack + + ## Create a new Global stack + # + # \param new_stack_id The ID of the new stack. + # \param definition The definition to base the new stack on. + # \param kwargs You can add keyword arguments to specify IDs of containers to use for a specific type, for example "variant": "0.4mm" + # + # \return A new Global stack instance with the specified parameters. + @classmethod + def createGlobalStack(cls, new_stack_id: str, definition: DefinitionContainer, **kwargs) -> GlobalStack: + stack = GlobalStack(new_stack_id) + stack.setDefinition(definition) + + user_container = InstanceContainer(new_stack_id + "_user") + user_container.addMetaDataEntry("type", "user") + user_container.addMetaDataEntry("machine", new_stack_id) + user_container.setDefinition(definition) + + stack.setUserChanges(user_container) + + # Important! The order here matters, because that allows the stack to + # assume the material and variant have already been set. + if "definition_changes" in kwargs: + stack.setDefinitionChangesById(kwargs["definition_changes"]) + + if "variant" in kwargs: + stack.setVariantById(kwargs["variant"]) + + if "material" in kwargs: + stack.setMaterialById(kwargs["material"]) + + if "quality" in kwargs: + stack.setQualityById(kwargs["quality"]) + + if "quality_changes" in kwargs: + stack.setQualityChangesById(kwargs["quality_changes"]) + + registry = ContainerRegistry.getInstance() + registry.addContainer(stack) + registry.addContainer(user_container) + + return stack diff --git a/cura/Settings/Exceptions.py b/cura/Settings/Exceptions.py new file mode 100644 index 0000000000..a30059b2e7 --- /dev/null +++ b/cura/Settings/Exceptions.py @@ -0,0 +1,22 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + + +## Raised when trying to perform an operation like add on a stack that does not allow that. +class InvalidOperationError(Exception): + pass + + +## Raised when trying to replace a container with a container that does not have the expected type. +class InvalidContainerError(Exception): + pass + + +## Raised when trying to add an extruder to a Global stack that already has the maximum number of extruders. +class TooManyExtrudersError(Exception): + pass + + +## Raised when an extruder has no next stack set. +class NoGlobalStackError(Exception): + pass diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 21cd164ed4..359cd74f09 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -6,6 +6,7 @@ from UM.FlameProfiler import pyqtSlot from UM.Application import Application #To get the global container stack to find the current machine. from UM.Logger import Logger +from UM.Decorators import deprecated from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.SceneNode import SceneNode from UM.Scene.Selection import Selection @@ -34,7 +35,7 @@ class ExtruderManager(QObject): ## Registers listeners and such to listen to changes to the extruders. def __init__(self, parent = None): super().__init__(parent) - self._extruder_trains = { } #Per machine, a dictionary of extruder container stack IDs. + self._extruder_trains = { } #Per machine, a dictionary of extruder container stack IDs. Only for separately defined extruders. self._active_extruder_index = 0 self._selected_object_extruders = [] Application.getInstance().globalContainerStackChanged.connect(self.__globalContainerStackChanged) @@ -150,7 +151,8 @@ class ExtruderManager(QObject): object_extruders.add(extruder) else: global_stack = Application.getInstance().getGlobalContainerStack() - object_extruders.add(self._extruder_trains[global_stack.getId()]["0"].getId()) + if global_stack.getId() in self._extruder_trains: + object_extruders.add(self._extruder_trains[global_stack.getId()]["0"].getId()) self._selected_object_extruders = list(object_extruders) @@ -194,6 +196,7 @@ class ExtruderManager(QObject): # # \param machine_definition The machine definition to add the extruders for. # \param machine_id The machine_id to add the extruders for. + @deprecated("Use CuraStackBuilder", "2.6") def addMachineExtruders(self, machine_definition: DefinitionContainer, machine_id: str) -> None: changed = False machine_definition_id = machine_definition.getId() @@ -246,6 +249,7 @@ class ExtruderManager(QObject): # \param machine_definition The machine that the extruder train belongs to. # \param position The position of this extruder train in the extruder slots of the machine. # \param machine_id The id of the "global" stack this extruder is linked to. + @deprecated("Use CuraStackBuilder::createExtruderStack", "2.6") def createExtruderTrain(self, extruder_definition: DefinitionContainer, machine_definition: DefinitionContainer, position, machine_id: str) -> None: # Cache some things. @@ -459,7 +463,6 @@ class ExtruderManager(QObject): # \param machine_id The machine to get the extruders of. def getMachineExtruders(self, machine_id): if machine_id not in self._extruder_trains: - Logger.log("w", "Tried to get the extruder trains for machine %s, which doesn't exist.", machine_id) return [] return [self._extruder_trains[machine_id][name] for name in self._extruder_trains[machine_id]] @@ -483,13 +486,12 @@ class ExtruderManager(QObject): global_stack = Application.getInstance().getGlobalContainerStack() result = [] - if global_stack: + if global_stack and global_stack.getId() in self._extruder_trains: for extruder in sorted(self._extruder_trains[global_stack.getId()]): result.append(self._extruder_trains[global_stack.getId()][extruder]) return result def __globalContainerStackChanged(self) -> None: - self._addCurrentMachineExtruders() global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack and global_container_stack.getBottom() and global_container_stack.getBottom().getId() != self._global_container_stack_definition_id: self._global_container_stack_definition_id = global_container_stack.getBottom().getId() @@ -518,6 +520,10 @@ class ExtruderManager(QObject): result = [] for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()): + # only include values from extruders that are "active" for the current machine instance + if int(extruder.getMetaDataEntry("position")) >= global_stack.getProperty("machine_extruder_count", "value"): + continue + value = extruder.getRawProperty(key, "value") if value is None: @@ -576,18 +582,6 @@ class ExtruderManager(QObject): @staticmethod def getResolveOrValue(key): global_stack = Application.getInstance().getGlobalContainerStack() + resolved_value = global_stack.getProperty(key, "value") - resolved_value = global_stack.getProperty(key, "resolve") - if resolved_value is not None: - user_container = global_stack.findContainer({"type": "user"}) - quality_changes_container = global_stack.findContainer({"type": "quality_changes"}) - if user_container.hasProperty(key, "value") or quality_changes_container.hasProperty(key, "value"): - # Normal case - value = global_stack.getProperty(key, "value") - else: - # We have a resolved value and we're using it because of no user and quality_changes value - value = resolved_value - else: - value = global_stack.getRawProperty(key, "value") - - return value + return resolved_value diff --git a/cura/Settings/ExtruderStack.py b/cura/Settings/ExtruderStack.py new file mode 100644 index 0000000000..18a9969828 --- /dev/null +++ b/cura/Settings/ExtruderStack.py @@ -0,0 +1,85 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + +from typing import Any + +from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot + +from UM.Decorators import override +from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase +from UM.Settings.ContainerStack import ContainerStack, InvalidContainerStackError +from UM.Settings.ContainerRegistry import ContainerRegistry +from UM.Settings.InstanceContainer import InstanceContainer +from UM.Settings.DefinitionContainer import DefinitionContainer +from UM.Settings.Interfaces import ContainerInterface + +from . import Exceptions +from .CuraContainerStack import CuraContainerStack +from .ExtruderManager import ExtruderManager + +## Represents an Extruder and its related containers. +# +# +class ExtruderStack(CuraContainerStack): + def __init__(self, container_id, *args, **kwargs): + super().__init__(container_id, *args, **kwargs) + + self.addMetaDataEntry("type", "extruder_train") # For backward compatibility + + ## Overridden from ContainerStack + # + # This will set the next stack and ensure that we register this stack as an extruder. + @override(ContainerStack) + def setNextStack(self, stack: ContainerStack) -> None: + super().setNextStack(stack) + stack.addExtruder(self) + self.addMetaDataEntry("machine", stack.id) + + # For backward compatibility: Register the extruder with the Extruder Manager + ExtruderManager.getInstance().registerExtruder(self, stack.id) + + @classmethod + def getLoadingPriority(cls) -> int: + return 3 + + ## Overridden from ContainerStack + # + # It will perform a few extra checks when trying to get properties. + # + # The two extra checks it currently does is to ensure a next stack is set and to bypass + # the extruder when the property is not settable per extruder. + # + # \throws Exceptions.NoGlobalStackError Raised when trying to get a property from an extruder without + # having a next stack set. + @override(ContainerStack) + def getProperty(self, key: str, property_name: str) -> Any: + if not self._next_stack: + raise Exceptions.NoGlobalStackError("Extruder {id} is missing the next stack!".format(id = self.id)) + + if not super().getProperty(key, "settable_per_extruder"): + return self.getNextStack().getProperty(key, property_name) + + return super().getProperty(key, property_name) + + @override(CuraContainerStack) + def _getMachineDefinition(self) -> ContainerInterface: + if not self.getNextStack(): + raise Exceptions.NoGlobalStackError("Extruder {id} is missing the next stack!".format(id = self.id)) + + return self.getNextStack()._getMachineDefinition() + + @override(CuraContainerStack) + def deserialize(self, contents: str) -> None: + super().deserialize(contents) + stacks = ContainerRegistry.getInstance().findContainerStacks(id=self.getMetaDataEntry("machine", "")) + if stacks: + self.setNextStack(stacks[0]) + +extruder_stack_mime = MimeType( + name = "application/x-cura-extruderstack", + comment = "Cura Extruder Stack", + suffixes = ["extruder.cfg"] +) + +MimeTypeDatabase.addMimeType(extruder_stack_mime) +ContainerRegistry.addContainerTypeByName(ExtruderStack, "extruder_stack", extruder_stack_mime.name) diff --git a/cura/Settings/ExtrudersModel.py b/cura/Settings/ExtrudersModel.py index 62b1f0af2c..d2922ebcc5 100644 --- a/cura/Settings/ExtrudersModel.py +++ b/cura/Settings/ExtrudersModel.py @@ -1,11 +1,11 @@ # Copyright (c) 2016 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. -from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty, pyqtSlot +from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty, QTimer import UM.Qt.ListModel from UM.Application import Application - +import UM.FlameProfiler from cura.Settings.ExtruderManager import ExtruderManager ## Model that holds extruders. @@ -58,6 +58,11 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): self.addRoleName(self.MaterialRole, "material") self.addRoleName(self.VariantRole, "variant") + self._update_extruder_timer = QTimer() + self._update_extruder_timer.setInterval(250) + self._update_extruder_timer.setSingleShot(True) + self._update_extruder_timer.timeout.connect(self.__updateExtruders) + self._add_global = False self._simple_names = False @@ -111,28 +116,33 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): active_extruder_stack.containersChanged.connect(self._onExtruderStackContainersChanged) self._active_extruder_stack = active_extruder_stack - def _onExtruderStackContainersChanged(self, container): - # The ExtrudersModel needs to be updated when the material-name or -color changes, because the user identifies extruders by material-name - self._updateExtruders() + # Update when there is an empty container or material change + if container.getMetaDataEntry("type") == "material" or container.getMetaDataEntry("type") is None: + # The ExtrudersModel needs to be updated when the material-name or -color changes, because the user identifies extruders by material-name + self._updateExtruders() + modelChanged = pyqtSignal() + def _updateExtruders(self): + self._update_extruder_timer.start() + ## Update the list of extruders. # # This should be called whenever the list of extruders changes. - def _updateExtruders(self): + @UM.FlameProfiler.profile + def __updateExtruders(self): changed = False if self.rowCount() != 0: changed = True items = [] - global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: if self._add_global: - material = global_container_stack.findContainer({ "type": "material" }) + material = global_container_stack.material color = material.getMetaDataEntry("color_code", default = self.defaultColors[0]) if material else self.defaultColors[0] item = { "id": global_container_stack.getId(), @@ -144,16 +154,20 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): items.append(item) changed = True + machine_extruder_count = global_container_stack.getProperty("machine_extruder_count", "value") manager = ExtruderManager.getInstance() for extruder in manager.getMachineExtruders(global_container_stack.getId()): - extruder_name = extruder.getName() - material = extruder.findContainer({ "type": "material" }) - variant = extruder.findContainer({"type": "variant"}) position = extruder.getMetaDataEntry("position", default = "0") # Get the position try: position = int(position) except ValueError: #Not a proper int. position = -1 + if position >= machine_extruder_count: + continue + extruder_name = extruder.getName() + material = extruder.material + variant = extruder.variant + default_color = self.defaultColors[position] if position >= 0 and position < len(self.defaultColors) else self.defaultColors[0] color = material.getMetaDataEntry("color_code", default = default_color) if material else default_color item = { #Construct an item with only the relevant information. diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py new file mode 100755 index 0000000000..cee141cf93 --- /dev/null +++ b/cura/Settings/GlobalStack.py @@ -0,0 +1,123 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + +from typing import Any + +from PyQt5.QtCore import pyqtProperty + +from UM.Decorators import override + +from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase +from UM.Settings.ContainerStack import ContainerStack +from UM.Settings.SettingInstance import InstanceState +from UM.Settings.ContainerRegistry import ContainerRegistry +from UM.Logger import Logger + +from . import Exceptions +from .CuraContainerStack import CuraContainerStack + +## Represents the Global or Machine stack and its related containers. +# +class GlobalStack(CuraContainerStack): + def __init__(self, container_id: str, *args, **kwargs): + super().__init__(container_id, *args, **kwargs) + + self.addMetaDataEntry("type", "machine") # For backward compatibility + + self._extruders = [] + + # This property is used to track which settings we are calculating the "resolve" for + # and if so, to bypass the resolve to prevent an infinite recursion that would occur + # if the resolve function tried to access the same property it is a resolve for. + self._resolving_settings = set() + + ## Get the list of extruders of this stack. + # + # \return The extruders registered with this stack. + @pyqtProperty("QVariantList") + def extruders(self) -> list: + return self._extruders + + @classmethod + def getLoadingPriority(cls) -> int: + return 2 + + ## Add an extruder to the list of extruders of this stack. + # + # \param extruder The extruder to add. + # + # \throws Exceptions.TooManyExtrudersError Raised when trying to add an extruder while we + # already have the maximum number of extruders. + def addExtruder(self, extruder: ContainerStack) -> None: + extruder_count = self.getProperty("machine_extruder_count", "value") + if extruder_count and len(self._extruders) + 1 > extruder_count: + Logger.log("w", "Adding extruder {meta} to {id} but its extruder count is {count}".format(id = self.id, count = extruder_count, meta = str(extruder.getMetaData()))) + + self._extruders.append(extruder) + + ## Overridden from ContainerStack + # + # This will return the value of the specified property for the specified setting, + # unless the property is "value" and that setting has a "resolve" function set. + # When a resolve is set, it will instead try and execute the resolve first and + # then fall back to the normal "value" property. + # + # \param key The setting key to get the property of. + # \param property_name The property to get the value of. + # + # \return The value of the property for the specified setting, or None if not found. + @override(ContainerStack) + def getProperty(self, key: str, property_name: str) -> Any: + if not self.definition.findDefinitions(key = key): + return None + + if self._shouldResolve(key, property_name): + self._resolving_settings.add(key) + resolve = super().getProperty(key, "resolve") + self._resolving_settings.remove(key) + if resolve is not None: + return resolve + + return super().getProperty(key, property_name) + + ## Overridden from ContainerStack + # + # This will simply raise an exception since the Global stack cannot have a next stack. + @override(ContainerStack) + def setNextStack(self, next_stack: ContainerStack) -> None: + raise Exceptions.InvalidOperationError("Global stack cannot have a next stack!") + + # protected: + + # Determine whether or not we should try to get the "resolve" property instead of the + # requested property. + def _shouldResolve(self, key: str, property_name: str) -> bool: + if property_name is not "value": + # Do not try to resolve anything but the "value" property + return False + + if key in self._resolving_settings: + # To prevent infinite recursion, if getProperty is called with the same key as + # we are already trying to resolve, we should not try to resolve again. Since + # this can happen multiple times when trying to resolve a value, we need to + # track all settings that are being resolved. + return False + + setting_state = super().getProperty(key, "state") + if setting_state is not None and setting_state != InstanceState.Default: + # When the user has explicitly set a value, we should ignore any resolve and + # just return that value. + return False + + return True + + +## private: +global_stack_mime = MimeType( + name = "application/x-cura-globalstack", + comment = "Cura Global Stack", + suffixes = ["global.cfg"] +) + +MimeTypeDatabase.addMimeType(global_stack_mime) +ContainerRegistry.addContainerTypeByName(GlobalStack, "global_stack", global_stack_mime.name) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 493f8fcf07..9d66bc7030 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -11,17 +11,24 @@ from UM.Application import Application from UM.Preferences import Preferences from UM.Logger import Logger from UM.Message import Message +from UM.Decorators import deprecated from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerStack import ContainerStack from UM.Settings.InstanceContainer import InstanceContainer +from UM.Settings.SettingDefinition import SettingDefinition from UM.Settings.SettingFunction import SettingFunction +from UM.Settings.Validator import ValidatorState from UM.Signal import postponeSignals +import UM.FlameProfiler from cura.QualityManager import QualityManager from cura.PrinterOutputDevice import PrinterOutputDevice from cura.Settings.ExtruderManager import ExtruderManager +from .GlobalStack import GlobalStack +from .CuraStackBuilder import CuraStackBuilder + from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") @@ -39,6 +46,16 @@ class MachineManager(QObject): self._active_container_stack = None # type: ContainerStack self._global_container_stack = None # type: ContainerStack + self._error_check_timer = QTimer() + self._error_check_timer.setInterval(250) + self._error_check_timer.setSingleShot(True) + self._error_check_timer.timeout.connect(self._updateStacksHaveErrors) + + self._instance_container_timer = QTimer() + self._instance_container_timer.setInterval(250) + self._instance_container_timer.setSingleShot(True) + self._instance_container_timer.timeout.connect(self.__onInstanceContainersChanged) + Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged) ## When the global container is changed, active material probably needs to be updated. self.globalContainerChanged.connect(self.activeMaterialChanged) @@ -46,10 +63,12 @@ class MachineManager(QObject): self.globalContainerChanged.connect(self.activeQualityChanged) self._stacks_have_errors = None - self._empty_variant_container = ContainerRegistry.getInstance().findInstanceContainers(id="empty_variant")[0] - self._empty_material_container = ContainerRegistry.getInstance().findInstanceContainers(id="empty_material")[0] - self._empty_quality_container = ContainerRegistry.getInstance().findInstanceContainers(id="empty_quality")[0] - self._empty_quality_changes_container = ContainerRegistry.getInstance().findInstanceContainers(id="empty_quality_changes")[0] + + self._empty_variant_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() + self._empty_material_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() + self._empty_quality_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() + self._empty_quality_changes_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() + self._onGlobalContainerChanged() ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged) @@ -86,10 +105,7 @@ class MachineManager(QObject): self._material_incompatible_message = Message(catalog.i18nc("@info:status", "The selected material is incompatible with the selected machine or configuration.")) - self._error_check_timer = QTimer() - self._error_check_timer.setInterval(250) - self._error_check_timer.setSingleShot(True) - self._error_check_timer.timeout.connect(self._updateStacksHaveErrors) + globalContainerChanged = pyqtSignal() # Emitted whenever the global stack is changed (ie: when changing between printers, changing a global profile, but not when changing a value) activeMaterialChanged = pyqtSignal() @@ -226,14 +242,22 @@ class MachineManager(QObject): def _onGlobalContainerChanged(self): if self._global_container_stack: - self._global_container_stack.nameChanged.disconnect(self._onMachineNameChanged) - self._global_container_stack.containersChanged.disconnect(self._onInstanceContainersChanged) - self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged) - - material = self._global_container_stack.findContainer({"type": "material"}) + try: + self._global_container_stack.nameChanged.disconnect(self._onMachineNameChanged) + except TypeError: #pyQtSignal gives a TypeError when disconnecting from something that was already disconnected. + pass + try: + self._global_container_stack.containersChanged.disconnect(self._onInstanceContainersChanged) + except TypeError: + pass + try: + self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged) + except TypeError: + pass + material = self._global_container_stack.material material.nameChanged.disconnect(self._onMaterialNameChanged) - quality = self._global_container_stack.findContainer({"type": "quality"}) + quality = self._global_container_stack.quality quality.nameChanged.disconnect(self._onQualityNameChanged) if self._global_container_stack.getProperty("machine_extruder_count", "value") > 1: @@ -256,26 +280,25 @@ class MachineManager(QObject): # For multi-extrusion machines, we do not want variant or material profiles in the stack, # because these are extruder specific and may cause wrong values to be used for extruders # that did not specify a value in the extruder. - global_variant = self._global_container_stack.findContainer(type = "variant") + global_variant = self._global_container_stack.variant if global_variant != self._empty_variant_container: - self._global_container_stack.replaceContainer(self._global_container_stack.getContainerIndex(global_variant), self._empty_variant_container) + self._global_container_stack.setVariant(self._empty_variant_container) - global_material = self._global_container_stack.findContainer(type = "material") + global_material = self._global_container_stack.material if global_material != self._empty_material_container: - self._global_container_stack.replaceContainer(self._global_container_stack.getContainerIndex(global_material), self._empty_material_container) + self._global_container_stack.setMaterial(self._empty_material_container) for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks(): #Listen for changes on all extruder stacks. extruder_stack.propertyChanged.connect(self._onPropertyChanged) extruder_stack.containersChanged.connect(self._onInstanceContainersChanged) else: - material = self._global_container_stack.findContainer({"type": "material"}) + material = self._global_container_stack.material material.nameChanged.connect(self._onMaterialNameChanged) - quality = self._global_container_stack.findContainer({"type": "quality"}) + quality = self._global_container_stack.quality quality.nameChanged.connect(self._onQualityNameChanged) - - self._updateStacksHaveErrors() + self._error_check_timer.start() ## Update self._stacks_valid according to _checkStacksForErrors and emit if change. def _updateStacksHaveErrors(self): @@ -292,21 +315,21 @@ class MachineManager(QObject): if not self._active_container_stack: self._active_container_stack = self._global_container_stack - self._updateStacksHaveErrors() + self._error_check_timer.start() if old_active_container_stack != self._active_container_stack: # Many methods and properties related to the active quality actually depend # on _active_container_stack. If it changes, then the properties change. self.activeQualityChanged.emit() - def _onInstanceContainersChanged(self, container): - container_type = container.getMetaDataEntry("type") - + def __onInstanceContainersChanged(self): + self.activeQualityChanged.emit() self.activeVariantChanged.emit() self.activeMaterialChanged.emit() - self.activeQualityChanged.emit() + self._error_check_timer.start() - self._updateStacksHaveErrors() + def _onInstanceContainersChanged(self, container): + self._instance_container_timer.start() def _onPropertyChanged(self, key, property_name): if property_name == "value": @@ -325,41 +348,11 @@ class MachineManager(QObject): @pyqtSlot(str, str) def addMachine(self, name: str, definition_id: str) -> None: - container_registry = ContainerRegistry.getInstance() - definitions = container_registry.findDefinitionContainers(id = definition_id) - if definitions: - definition = definitions[0] - name = self._createUniqueName("machine", "", name, definition.getName()) - new_global_stack = ContainerStack(name) - new_global_stack.addMetaDataEntry("type", "machine") - new_global_stack.addContainer(definition) - container_registry.addContainer(new_global_stack) - - variant_instance_container = self._updateVariantContainer(definition) - material_instance_container = self._updateMaterialContainer(definition, new_global_stack, variant_instance_container) - quality_instance_container = self._updateQualityContainer(definition, variant_instance_container, material_instance_container) - - current_settings_instance_container = InstanceContainer(name + "_current_settings") - current_settings_instance_container.addMetaDataEntry("machine", name) - current_settings_instance_container.addMetaDataEntry("type", "user") - current_settings_instance_container.setDefinition(definitions[0]) - container_registry.addContainer(current_settings_instance_container) - - - if variant_instance_container: - new_global_stack.addContainer(variant_instance_container) - if material_instance_container: - new_global_stack.addContainer(material_instance_container) - if quality_instance_container: - new_global_stack.addContainer(quality_instance_container) - - new_global_stack.addContainer(self._empty_quality_changes_container) - new_global_stack.addContainer(current_settings_instance_container) - - ExtruderManager.getInstance().addMachineExtruders(definition, new_global_stack.getId()) - - Application.getInstance().setGlobalContainerStack(new_global_stack) - + new_stack = CuraStackBuilder.createMachine(name, definition_id) + if new_stack: + Application.getInstance().setGlobalContainerStack(new_stack) + else: + Logger.log("w", "Failed creating a new machine!") ## Create a name that is not empty and unique # \param container_type \type{string} Type of the container (machine, quality, ...) @@ -478,6 +471,10 @@ class MachineManager(QObject): return "" + @pyqtProperty("QObject", notify = globalContainerChanged) + def activeMachine(self) -> GlobalStack: + return self._global_container_stack + @pyqtProperty(str, notify = activeStackChanged) def activeStackId(self) -> str: if self._active_container_stack: @@ -488,7 +485,7 @@ class MachineManager(QObject): @pyqtProperty(str, notify = activeMaterialChanged) def activeMaterialName(self) -> str: if self._active_container_stack: - material = self._active_container_stack.findContainer({"type":"material"}) + material = self._active_container_stack.material if material: return material.getName() @@ -499,18 +496,29 @@ class MachineManager(QObject): result = [] if ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() is not None: for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks(): - variant_container = stack.findContainer({"type": "variant"}) + variant_container = stack.variant if variant_container and variant_container != self._empty_variant_container: result.append(variant_container.getName()) return result + @pyqtProperty("QVariantList", notify = activeVariantChanged) + def activeMaterialIds(self): + result = [] + if ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() is not None: + for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks(): + variant_container = stack.findContainer({"type": "variant"}) + if variant_container and variant_container != self._empty_variant_container: + result.append(variant_container.getId()) + + return result + @pyqtProperty("QVariantList", notify = activeMaterialChanged) def activeMaterialNames(self): result = [] if ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() is not None: for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks(): - material_container = stack.findContainer(type="material") + material_container = stack.material if material_container and material_container != self._empty_material_container: result.append(material_container.getName()) return result @@ -518,12 +526,28 @@ class MachineManager(QObject): @pyqtProperty(str, notify=activeMaterialChanged) def activeMaterialId(self) -> str: if self._active_container_stack: - material = self._active_container_stack.findContainer({"type": "material"}) + material = self._active_container_stack.material if material: return material.getId() return "" + @pyqtProperty("QVariantMap", notify = activeVariantChanged) + def allActiveVariantIds(self): + if not self._global_container_stack: + return {} + + result = {} + + for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks(): + variant_container = stack.variant + if not variant_container: + continue + + result[stack.getId()] = variant_container.getId() + + return result + @pyqtProperty("QVariantMap", notify = activeMaterialChanged) def allActiveMaterialIds(self): if not self._global_container_stack: @@ -532,7 +556,7 @@ class MachineManager(QObject): result = {} for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks(): - material_container = stack.findContainer(type = "material") + material_container = stack.material if not material_container: continue @@ -551,13 +575,13 @@ class MachineManager(QObject): if not self._global_container_stack: return 0 - quality_changes = self._global_container_stack.findContainer({"type": "quality_changes"}) + quality_changes = self._global_container_stack.qualityChanges if quality_changes: value = self._global_container_stack.getRawProperty("layer_height", "value", skip_until_container = quality_changes.getId()) if isinstance(value, SettingFunction): value = value(self._global_container_stack) return value - quality = self._global_container_stack.findContainer({"type": "quality"}) + quality = self._global_container_stack.quality if quality: value = self._global_container_stack.getRawProperty("layer_height", "value", skip_until_container = quality.getId()) if isinstance(value, SettingFunction): @@ -571,7 +595,7 @@ class MachineManager(QObject): @pyqtProperty(str, notify=activeQualityChanged) def activeQualityMaterialId(self) -> str: if self._active_container_stack: - quality = self._active_container_stack.findContainer({"type": "quality"}) + quality = self._active_container_stack.quality if quality: material_id = quality.getMetaDataEntry("material") if material_id: @@ -588,10 +612,10 @@ class MachineManager(QObject): @pyqtProperty(str, notify=activeQualityChanged) def activeQualityName(self): if self._active_container_stack and self._global_container_stack: - quality = self._global_container_stack.findContainer({"type": "quality_changes"}) - if quality and quality != self._empty_quality_changes_container: + quality = self._global_container_stack.qualityChanges + if quality and not isinstance(quality, type(self._empty_quality_changes_container)): return quality.getName() - quality = self._active_container_stack.findContainer({"type": "quality"}) + quality = self._active_container_stack.quality if quality: return quality.getName() return "" @@ -599,10 +623,10 @@ class MachineManager(QObject): @pyqtProperty(str, notify=activeQualityChanged) def activeQualityId(self): if self._active_container_stack: - quality = self._active_container_stack.findContainer({"type": "quality_changes"}) - if quality and quality != self._empty_quality_changes_container: + quality = self._active_container_stack.qualityChanges + if quality and not isinstance(quality, type(self._empty_quality_changes_container)): return quality.getId() - quality = self._active_container_stack.findContainer({"type": "quality"}) + quality = self._active_container_stack.quality if quality: return quality.getId() return "" @@ -610,10 +634,10 @@ class MachineManager(QObject): @pyqtProperty(str, notify=activeQualityChanged) def globalQualityId(self): if self._global_container_stack: - quality = self._global_container_stack.findContainer({"type": "quality_changes"}) - if quality and quality != self._empty_quality_changes_container: + quality = self._global_container_stack.qualityChanges + if quality and not isinstance(quality, type(self._empty_quality_changes_container)): return quality.getId() - quality = self._global_container_stack.findContainer({"type": "quality"}) + quality = self._global_container_stack.quality if quality: return quality.getId() return "" @@ -621,7 +645,7 @@ class MachineManager(QObject): @pyqtProperty(str, notify = activeQualityChanged) def activeQualityType(self): if self._active_container_stack: - quality = self._active_container_stack.findContainer(type = "quality") + quality = self._active_container_stack.quality if quality: return quality.getMetaDataEntry("quality_type") return "" @@ -629,7 +653,7 @@ class MachineManager(QObject): @pyqtProperty(bool, notify = activeQualityChanged) def isActiveQualitySupported(self): if self._active_container_stack: - quality = self._active_container_stack.findContainer(type = "quality") + quality = self._active_container_stack.quality if quality: return Util.parseBool(quality.getMetaDataEntry("supported", True)) return False @@ -644,7 +668,7 @@ class MachineManager(QObject): def activeQualityContainerId(self): # We're using the active stack instead of the global stack in case the list of qualities differs per extruder if self._global_container_stack: - quality = self._active_container_stack.findContainer(type = "quality") + quality = self._active_container_stack.quality if quality: return quality.getId() return "" @@ -652,8 +676,8 @@ class MachineManager(QObject): @pyqtProperty(str, notify = activeQualityChanged) def activeQualityChangesId(self): if self._active_container_stack: - changes = self._active_container_stack.findContainer(type = "quality_changes") - if changes: + changes = self._active_container_stack.qualityChanges + if changes and changes.getId() != "empty": return changes.getId() return "" @@ -690,21 +714,20 @@ class MachineManager(QObject): Logger.log("d", "Attempting to change the active material to %s", material_id) - old_material = self._active_container_stack.findContainer({"type": "material"}) - old_quality = self._active_container_stack.findContainer({"type": "quality"}) - old_quality_changes = self._active_container_stack.findContainer({"type": "quality_changes"}) + old_material = self._active_container_stack.material + old_quality = self._active_container_stack.quality + old_quality_changes = self._active_container_stack.qualityChanges if not old_material: Logger.log("w", "While trying to set the active material, no material was found to replace it.") return - if old_quality_changes.getId() == "empty_quality_changes": + if old_quality_changes and old_quality_changes.getId() == "empty_quality_changes": old_quality_changes = None self.blurSettings.emit() old_material.nameChanged.disconnect(self._onMaterialNameChanged) - material_index = self._active_container_stack.getContainerIndex(old_material) - self._active_container_stack.replaceContainer(material_index, material_container) + self._active_container_stack.material = material_container Logger.log("d", "Active material changed") material_container.nameChanged.connect(self._onMaterialNameChanged) @@ -753,13 +776,12 @@ class MachineManager(QObject): if not containers or not self._active_container_stack: return Logger.log("d", "Attempting to change the active variant to %s", variant_id) - old_variant = self._active_container_stack.findContainer({"type": "variant"}) - old_material = self._active_container_stack.findContainer({"type": "material"}) + old_variant = self._active_container_stack.variant + old_material = self._active_container_stack.material if old_variant: self.blurSettings.emit() - variant_index = self._active_container_stack.getContainerIndex(old_variant) - self._active_container_stack.replaceContainer(variant_index, containers[0]) - Logger.log("d", "Active variant changed") + self._active_container_stack.variant = containers[0] + Logger.log("d", "Active variant changed to {active_variant_id}".format(active_variant_id = containers[0].getId())) preferred_material = None if old_material: preferred_material_name = old_material.getName() @@ -808,8 +830,8 @@ class MachineManager(QObject): name_changed_connect_stacks.append(stack_quality) name_changed_connect_stacks.append(stack_quality_changes) - self._replaceQualityOrQualityChangesInStack(stack, stack_quality) - self._replaceQualityOrQualityChangesInStack(stack, stack_quality_changes) + self._replaceQualityOrQualityChangesInStack(stack, stack_quality, postpone_emit=True) + self._replaceQualityOrQualityChangesInStack(stack, stack_quality_changes, postpone_emit=True) # Send emits that are postponed in replaceContainer. # Here the stacks are finished replacing and every value can be resolved based on the current state. @@ -829,6 +851,7 @@ class MachineManager(QObject): # # \param quality_name \type{str} the name of the quality. # \return \type{List[Dict]} with keys "stack", "quality" and "quality_changes". + @UM.FlameProfiler.profile def determineQualityAndQualityChangesForQualityType(self, quality_type): quality_manager = QualityManager.getInstance() result = [] @@ -845,7 +868,7 @@ class MachineManager(QObject): stacks = [global_container_stack] for stack in stacks: - material = stack.findContainer(type="material") + material = stack.material quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material]) if not quality: #No quality profile is found for this quality type. quality = self._empty_quality_container @@ -882,7 +905,7 @@ class MachineManager(QObject): else: Logger.log("e", "Could not find the global quality changes container with name %s", quality_changes_name) return None - material = global_container_stack.findContainer(type="material") + material = global_container_stack.material # For the global stack, find a quality which matches the quality_type in # the quality changes profile and also satisfies any material constraints. @@ -905,7 +928,7 @@ class MachineManager(QObject): else: quality_changes = global_quality_changes - material = stack.findContainer(type="material") + material = stack.material quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material]) if not quality: #No quality profile found for this quality type. quality = self._empty_quality_container @@ -924,18 +947,18 @@ class MachineManager(QObject): def _replaceQualityOrQualityChangesInStack(self, stack, container, postpone_emit = False): # Disconnect the signal handling from the old container. - old_container = stack.findContainer(type=container.getMetaDataEntry("type")) - if old_container: - old_container.nameChanged.disconnect(self._onQualityNameChanged) - else: - Logger.log("e", "Could not find container of type %s in stack %s while replacing quality (changes) with container %s", container.getMetaDataEntry("type"), stack.getId(), container.getId()) - return - - # Swap in the new container into the stack. - stack.replaceContainer(stack.getContainerIndex(old_container), container, postpone_emit = postpone_emit) - - # Attach the needed signal handling. - container.nameChanged.connect(self._onQualityNameChanged) + container_type = container.getMetaDataEntry("type") + if container_type == "quality": + stack.quality.nameChanged.disconnect(self._onQualityNameChanged) + stack.setQuality(container, postpone_emit = postpone_emit) + stack.qualityChanges.nameChanged.connect(self._onQualityNameChanged) + elif container_type == "quality_changes" or container_type is None: + # If the container is an empty container, we need to change the quality_changes. + # Quality can never be set to empty. + stack.qualityChanges.nameChanged.disconnect(self._onQualityNameChanged) + stack.setQualityChanges(container, postpone_emit = postpone_emit) + stack.qualityChanges.nameChanged.connect(self._onQualityNameChanged) + self._onQualityNameChanged() def _askUserToKeepOrClearCurrentSettings(self): Application.getInstance().discardOrKeepProfileChanges() @@ -943,7 +966,7 @@ class MachineManager(QObject): @pyqtProperty(str, notify = activeVariantChanged) def activeVariantName(self): if self._active_container_stack: - variant = self._active_container_stack.findContainer({"type": "variant"}) + variant = self._active_container_stack.variant if variant: return variant.getName() @@ -952,7 +975,7 @@ class MachineManager(QObject): @pyqtProperty(str, notify = activeVariantChanged) def activeVariantId(self): if self._active_container_stack: - variant = self._active_container_stack.findContainer({"type": "variant"}) + variant = self._active_container_stack.variant if variant: return variant.getId() @@ -998,7 +1021,7 @@ class MachineManager(QObject): @pyqtProperty(str, notify = activeVariantChanged) def activeQualityVariantId(self): if self._active_container_stack: - variant = self._active_container_stack.findContainer({"type": "variant"}) + variant = self._active_container_stack.variant if variant: return self.getQualityVariantId(self._global_container_stack.getBottom(), variant) return "" diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py old mode 100644 new mode 100755 index a0ce679464..e751211d04 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -15,7 +15,10 @@ from .WorkspaceDialog import WorkspaceDialog import xml.etree.ElementTree as ET from cura.Settings.ExtruderManager import ExtruderManager +from cura.Settings.ExtruderStack import ExtruderStack +from cura.Settings.GlobalStack import GlobalStack +from configparser import ConfigParser import zipfile import io import configparser @@ -31,10 +34,14 @@ class ThreeMFWorkspaceReader(WorkspaceReader): self._dialog = WorkspaceDialog() self._3mf_mesh_reader = None self._container_registry = ContainerRegistry.getInstance() - self._definition_container_suffix = ContainerRegistry.getMimeTypeForContainer(DefinitionContainer).preferredSuffix + + # suffixes registered with the MineTypes don't start with a dot '.' + self._definition_container_suffix = "." + ContainerRegistry.getMimeTypeForContainer(DefinitionContainer).preferredSuffix self._material_container_suffix = None # We have to wait until all other plugins are loaded before we can set it - self._instance_container_suffix = ContainerRegistry.getMimeTypeForContainer(InstanceContainer).preferredSuffix - self._container_stack_suffix = ContainerRegistry.getMimeTypeForContainer(ContainerStack).preferredSuffix + self._instance_container_suffix = "." + ContainerRegistry.getMimeTypeForContainer(InstanceContainer).preferredSuffix + self._container_stack_suffix = "." + ContainerRegistry.getMimeTypeForContainer(ContainerStack).preferredSuffix + self._extruder_stack_suffix = "." + ContainerRegistry.getMimeTypeForContainer(ExtruderStack).preferredSuffix + self._global_stack_suffix = "." + ContainerRegistry.getMimeTypeForContainer(GlobalStack).preferredSuffix self._resolve_strategies = {} @@ -47,6 +54,49 @@ class ThreeMFWorkspaceReader(WorkspaceReader): self._id_mapping[old_id] = self._container_registry.uniqueName(old_id) return self._id_mapping[old_id] + ## Separates the given file list into a list of GlobalStack files and a list of ExtruderStack files. + # + # In old versions, extruder stack files have the same suffix as container stack files ".stack.cfg". + # + def _determineGlobalAndExtruderStackFiles(self, project_file_name, file_list): + archive = zipfile.ZipFile(project_file_name, "r") + + global_stack_file_list = [name for name in file_list if name.endswith(self._global_stack_suffix)] + extruder_stack_file_list = [name for name in file_list if name.endswith(self._extruder_stack_suffix)] + + # separate container stack files and extruder stack files + files_to_determine = [name for name in file_list if name.endswith(self._container_stack_suffix)] + for file_name in files_to_determine: + # FIXME: HACK! + # We need to know the type of the stack file, but we can only know it if we deserialize it. + # The default ContainerStack.deserialize() will connect signals, which is not desired in this case. + # Since we know that the stack files are INI files, so we directly use the ConfigParser to parse them. + serialized = archive.open(file_name).read().decode("utf-8") + stack_config = ConfigParser() + stack_config.read_string(serialized) + + # sanity check + if not stack_config.has_option("metadata", "type"): + Logger.log("e", "%s in %s doesn't seem to be valid stack file", file_name, project_file_name) + continue + + stack_type = stack_config.get("metadata", "type") + if stack_type == "extruder_train": + extruder_stack_file_list.append(file_name) + elif stack_type == "machine": + global_stack_file_list.append(file_name) + else: + Logger.log("w", "Unknown container stack type '%s' from %s in %s", + stack_type, file_name, project_file_name) + + if len(global_stack_file_list) != 1: + raise RuntimeError("More than one global stack file found: [%s]" % str(global_stack_file_list)) + + return global_stack_file_list[0], extruder_stack_file_list + + ## read some info so we can make decisions + # \param file_name + # \param show_dialog In case we use preRead() to check if a file is a valid project file, we don't want to show a dialog. def preRead(self, file_name, show_dialog=True, *args, **kwargs): self._3mf_mesh_reader = Application.getInstance().getMeshFileHandler().getReaderForFile(file_name) if self._3mf_mesh_reader and self._3mf_mesh_reader.preRead(file_name) == WorkspaceReader.PreReadResult.accepted: @@ -63,23 +113,13 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # Check if there are any conflicts, so we can ask the user. archive = zipfile.ZipFile(file_name, "r") cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")] - container_stack_files = [name for name in cura_file_names if name.endswith(self._container_stack_suffix)] - self._resolve_strategies = {"machine": None, "quality_changes": None, "material": None} - machine_conflict = False - quality_changes_conflict = False - for container_stack_file in container_stack_files: - container_id = self._stripFileToId(container_stack_file) - serialized = archive.open(container_stack_file).read().decode("utf-8") - if machine_name == "": - machine_name = self._getMachineNameFromSerializedStack(serialized) - stacks = self._container_registry.findContainerStacks(id=container_id) - if stacks: - # Check if there are any changes at all in any of the container stacks. - id_list = self._getContainerIdListFromSerialized(serialized) - for index, container_id in enumerate(id_list): - if stacks[0].getContainer(index).getId() != container_id: - machine_conflict = True - Job.yieldThread() + + # A few lists of containers in this project files. + # When loading the global stack file, it may be associated with those containers, which may or may not be + # in Cura already, so we need to provide them as alternative search lists. + definition_container_list = [] + instance_container_list = [] + material_container_list = [] definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)] for definition_container_file in definition_container_files: @@ -93,6 +133,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader): else: definition_container = definitions[0] + definition_container_list.append(definition_container) + if definition_container.getMetaDataEntry("type") != "extruder": machine_type = definition_container.getName() variant_type_name = definition_container.getMetaDataEntry("variants_name", variant_type_name) @@ -131,6 +173,9 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # Deserialize InstanceContainer by converting read data from bytes to string instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8")) + + instance_container_list.append(instance_container) + container_type = instance_container.getMetaDataEntry("type") if container_type == "quality_changes": quality_name = instance_container.getName() @@ -151,6 +196,27 @@ class ThreeMFWorkspaceReader(WorkspaceReader): num_user_settings += len(instance_container._instances) Job.yieldThread() + + # Load ContainerStack files and ExtruderStack files + global_stack_file, extruder_stack_files = self._determineGlobalAndExtruderStackFiles( + file_name, cura_file_names) + self._resolve_strategies = {"machine": None, "quality_changes": None, "material": None} + machine_conflict = False + quality_changes_conflict = False + for container_stack_file in [global_stack_file] + extruder_stack_files: + container_id = self._stripFileToId(container_stack_file) + serialized = archive.open(container_stack_file).read().decode("utf-8") + if machine_name == "": + machine_name = self._getMachineNameFromSerializedStack(serialized) + stacks = self._container_registry.findContainerStacks(id = container_id) + if stacks: + # Check if there are any changes at all in any of the container stacks. + id_list = self._getContainerIdListFromSerialized(serialized) + for index, container_id in enumerate(id_list): + if stacks[0].getContainer(index).getId() != container_id: + machine_conflict = True + Job.yieldThread() + num_visible_settings = 0 try: temp_preferences = Preferences() @@ -196,9 +262,20 @@ class ThreeMFWorkspaceReader(WorkspaceReader): return WorkspaceReader.PreReadResult.cancelled self._resolve_strategies = self._dialog.getResult() + # Default values + for k, v in self._resolve_strategies.items(): + if v is None: + self._resolve_strategies[k] = "new" return WorkspaceReader.PreReadResult.accepted + ## Read the project file + # Add all the definitions / materials / quality changes that do not exist yet. Then it loads + # all the stacks into the container registry. In some cases it will reuse the container for the global stack. + # It handles old style project files containing .stack.cfg as well as new style project files + # containing global.cfg / extruder.cfg + # + # \param file_name def read(self, file_name): archive = zipfile.ZipFile(file_name, "r") @@ -232,6 +309,23 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # We do this so that if something goes wrong, it's easier to clean up. containers_to_add = [] + global_stack_file, extruder_stack_files = self._determineGlobalAndExtruderStackFiles(file_name, cura_file_names) + + global_stack = None + extruder_stacks = [] + extruder_stacks_added = [] + container_stacks_added = [] + + global_stack_id_original = self._stripFileToId(global_stack_file) + global_stack_id_new = global_stack_id_original + global_stack_need_rename = False + if self._resolve_strategies["machine"] == "new": + # We need a new id if the id already exists + if self._container_registry.findContainerStacks(id = global_stack_id_original): + global_stack_id_new = self.getNewId(global_stack_id_original) + global_stack_need_rename = True + + # TODO: For the moment we use pretty naive existence checking. If the ID is the same, we assume in quite a few # TODO: cases that the container loaded is the same (most notable in materials & definitions). # TODO: It might be possible that we need to add smarter checking in the future. @@ -240,7 +334,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)] for definition_container_file in definition_container_files: container_id = self._stripFileToId(definition_container_file) - definitions = self._container_registry.findDefinitionContainers(id=container_id) + definitions = self._container_registry.findDefinitionContainers(id = container_id) if not definitions: definition_container = DefinitionContainer(container_id) definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8")) @@ -257,7 +351,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): material_container_files = [name for name in cura_file_names if name.endswith(self._material_container_suffix)] for material_container_file in material_container_files: container_id = self._stripFileToId(material_container_file) - materials = self._container_registry.findInstanceContainers(id=container_id) + materials = self._container_registry.findInstanceContainers(id = container_id) if not materials: material_container = xml_material_profile(container_id) material_container.deserialize(archive.open(material_container_file).read().decode("utf-8")) @@ -279,7 +373,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # Get quality_changes and user profiles saved in the workspace instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)] user_instance_containers = [] - quality_changes_instance_containers = [] + quality_and_definition_changes_instance_containers = [] for instance_container_file in instance_container_files: container_id = self._stripFileToId(instance_container_file) instance_container = InstanceContainer(container_id) @@ -290,7 +384,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): Job.yieldThread() if container_type == "user": # Check if quality changes already exists. - user_containers = self._container_registry.findInstanceContainers(id=container_id) + user_containers = self._container_registry.findInstanceContainers(id = container_id) if not user_containers: containers_to_add.append(instance_container) else: @@ -314,20 +408,25 @@ class ThreeMFWorkspaceReader(WorkspaceReader): instance_container.setMetaDataEntry("machine", self.getNewId(machine_id)) containers_to_add.append(instance_container) user_instance_containers.append(instance_container) - elif container_type == "quality_changes": + elif container_type in ("quality_changes", "definition_changes"): # Check if quality changes already exists. - quality_changes = self._container_registry.findInstanceContainers(id = container_id) - if not quality_changes: + changes_containers = self._container_registry.findInstanceContainers(id = container_id) + if not changes_containers: containers_to_add.append(instance_container) else: - if self._resolve_strategies["quality_changes"] == "override": - quality_changes[0].deserialize(archive.open(instance_container_file).read().decode("utf-8")) - elif self._resolve_strategies["quality_changes"] is None: + if self._resolve_strategies[container_type] == "override": + changes_containers[0].deserialize(archive.open(instance_container_file).read().decode("utf-8")) + elif self._resolve_strategies[container_type] is None: # The ID already exists, but nothing in the values changed, so do nothing. pass - quality_changes_instance_containers.append(instance_container) + quality_and_definition_changes_instance_containers.append(instance_container) else: - continue + existing_container = self._container_registry.findInstanceContainers(id = container_id) + if not existing_container: + containers_to_add.append(instance_container) + if global_stack_need_rename: + if instance_container.getMetaDataEntry("machine"): + instance_container.setMetaDataEntry("machine", global_stack_id_new) # Add all the containers right before we try to add / serialize the stack for container in containers_to_add: @@ -336,68 +435,126 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # Get the stack(s) saved in the workspace. Logger.log("d", "Workspace loading is checking stacks containers...") - container_stack_files = [name for name in cura_file_names if name.endswith(self._container_stack_suffix)] - global_stack = None - extruder_stacks = [] - container_stacks_added = [] - try: - for container_stack_file in container_stack_files: - container_id = self._stripFileToId(container_stack_file) - # Check if a stack by this ID already exists; - container_stacks = self._container_registry.findContainerStacks(id=container_id) + # load extruder stack files + try: + for index, extruder_stack_file in enumerate(extruder_stack_files): + container_id = self._stripFileToId(extruder_stack_file) + + container_stacks = self._container_registry.findContainerStacks(id = container_id) if container_stacks: + # this container stack already exists, try to resolve stack = container_stacks[0] if self._resolve_strategies["machine"] == "override": - # TODO: HACK - # There is a machine, check if it has authenticationd data. If so, keep that data. - network_authentication_id = container_stacks[0].getMetaDataEntry("network_authentication_id") - network_authentication_key = container_stacks[0].getMetaDataEntry("network_authentication_key") - container_stacks[0].deserialize(archive.open(container_stack_file).read().decode("utf-8")) - if network_authentication_id: - container_stacks[0].addMetaDataEntry("network_authentication_id", network_authentication_id) - if network_authentication_key: - container_stacks[0].addMetaDataEntry("network_authentication_key", network_authentication_key) + pass # do nothing elif self._resolve_strategies["machine"] == "new": + # create a new extruder stack from this one new_id = self.getNewId(container_id) - stack = ContainerStack(new_id) - stack.deserialize(archive.open(container_stack_file).read().decode("utf-8")) + stack = ExtruderStack(new_id) + stack.deserialize(archive.open(extruder_stack_file).read().decode("utf-8")) # Ensure a unique ID and name stack._id = new_id - # Extruder stacks are "bound" to a machine. If we add the machine as a new one, the id of the - # bound machine also needs to change. - if stack.getMetaDataEntry("machine", None): - stack.setMetaDataEntry("machine", self.getNewId(stack.getMetaDataEntry("machine"))) - - if stack.getMetaDataEntry("type") != "extruder_train": - # Only machines need a new name, stacks may be non-unique - stack.setName(self._container_registry.uniqueName(stack.getName())) - container_stacks_added.append(stack) self._container_registry.addContainer(stack) - else: - Logger.log("w", "Resolve strategy of %s for machine is not supported", self._resolve_strategies["machine"]) + extruder_stacks_added.append(stack) else: - stack = ContainerStack(container_id) - # Deserialize stack by converting read data from bytes to string - stack.deserialize(archive.open(container_stack_file).read().decode("utf-8")) + if self._resolve_strategies["machine"] == "override": + global_stacks = self._container_registry.findContainerStacks(id = global_stack_id_original) + # deserialize new extruder stack over the current ones + if global_stacks: + old_extruder_stack_id = global_stacks[0].extruders[index].getId() + # HACK delete file + self._container_registry._deleteFiles(global_stacks[0].extruders[index]) + global_stacks[0].extruders[index].deserialize(archive.open(extruder_stack_file).read().decode("utf-8")) + # HACK + global_stacks[0]._extruders = global_stacks[0]._extruders[:2] + # HACK update cache + del self._container_registry._id_container_cache[old_extruder_stack_id] + new_extruder_stack_id = global_stacks[0].extruders[index].getId() + self._container_registry._id_container_cache[new_extruder_stack_id] = global_stacks[0].extruders[index] + + stack = global_stacks[0].extruders[index] + else: + Logger.log("w", "Could not find global stack, while I expected it: %s" % global_stack_id_original) + elif self._resolve_strategies["machine"] == "new": + # container not found, create a new one + stack = ExtruderStack(container_id) + stack.deserialize(archive.open(extruder_stack_file).read().decode("utf-8")) + self._container_registry.addContainer(stack) + extruder_stacks_added.append(stack) + else: + Logger.log("w", "Unknown resolve strategy: %s" % str(self._resolve_strategies["machine"])) + + if global_stack_need_rename: + if stack.getMetaDataEntry("machine"): + stack.setMetaDataEntry("machine", global_stack_id_new) + extruder_stacks.append(stack) + + except: + Logger.logException("w", "We failed to serialize the stack. Trying to clean up.") + # Something went really wrong. Try to remove any data that we added. + for container in extruder_stacks: + self._container_registry.removeContainer(container.getId()) + + return None + + # load global stack file + try: + # Check if a stack by this ID already exists; + container_stacks = self._container_registry.findContainerStacks(id = global_stack_id_original) + if container_stacks: + stack = container_stacks[0] + + if self._resolve_strategies["machine"] == "override": + # TODO: HACK + # There is a machine, check if it has authentication data. If so, keep that data. + network_authentication_id = container_stacks[0].getMetaDataEntry("network_authentication_id") + network_authentication_key = container_stacks[0].getMetaDataEntry("network_authentication_key") + container_stacks[0].deserialize(archive.open(global_stack_file).read().decode("utf-8")) + if network_authentication_id: + container_stacks[0].addMetaDataEntry("network_authentication_id", network_authentication_id) + if network_authentication_key: + container_stacks[0].addMetaDataEntry("network_authentication_key", network_authentication_key) + elif self._resolve_strategies["machine"] == "new": + stack = GlobalStack(global_stack_id_new) + stack.deserialize(archive.open(global_stack_file).read().decode("utf-8")) + + # Ensure a unique ID and name + stack._id = global_stack_id_new + + # Extruder stacks are "bound" to a machine. If we add the machine as a new one, the id of the + # bound machine also needs to change. + if stack.getMetaDataEntry("machine", None): + stack.setMetaDataEntry("machine", global_stack_id_new) + + # Only machines need a new name, stacks may be non-unique + stack.setName(self._container_registry.uniqueName(stack.getName())) container_stacks_added.append(stack) self._container_registry.addContainer(stack) - - if stack.getMetaDataEntry("type") == "extruder_train": - extruder_stacks.append(stack) else: - global_stack = stack - Job.yieldThread() + Logger.log("w", "Resolve strategy of %s for machine is not supported", self._resolve_strategies["machine"]) + else: + # no existing container stack, so we create a new one + stack = GlobalStack(global_stack_id_new) + # Deserialize stack by converting read data from bytes to string + stack.deserialize(archive.open(global_stack_file).read().decode("utf-8")) + container_stacks_added.append(stack) + self._container_registry.addContainer(stack) + + global_stack = stack + Job.yieldThread() except: Logger.logException("w", "We failed to serialize the stack. Trying to clean up.") # Something went really wrong. Try to remove any data that we added. for container in containers_to_add: - self._container_registry.getInstance().removeContainer(container.getId()) + self._container_registry.removeContainer(container.getId()) for container in container_stacks_added: - self._container_registry.getInstance().removeContainer(container.getId()) + self._container_registry.removeContainer(container.getId()) + + for container in extruder_stacks_added: + self._container_registry.removeContainer(container.getId()) return None @@ -416,29 +573,34 @@ class ThreeMFWorkspaceReader(WorkspaceReader): global_stack.replaceContainer(0, container) continue - if self._resolve_strategies["quality_changes"] == "new": - # Quality changes needs to get a new ID, added to registry and to the right stacks - for container in quality_changes_instance_containers: - old_id = container.getId() - container.setName(self._container_registry.uniqueName(container.getName())) - # We're not really supposed to change the ID in normal cases, but this is an exception. - container._id = self.getNewId(container.getId()) + for container_type in ("quality_changes", "definition_changes"): + if self._resolve_strategies[container_type] == "new": + # Quality changes needs to get a new ID, added to registry and to the right stacks + for container in quality_and_definition_changes_instance_containers: + old_id = container.getId() + container.setName(self._container_registry.uniqueName(container.getName())) + # We're not really supposed to change the ID in normal cases, but this is an exception. + container._id = self.getNewId(container.getId()) - # The container was not added yet, as it didn't have an unique ID. It does now, so add it. - self._container_registry.addContainer(container) + # The container was not added yet, as it didn't have an unique ID. It does now, so add it. + self._container_registry.addContainer(container) - # Replace the quality changes container - old_container = global_stack.findContainer({"type": "quality_changes"}) - if old_container.getId() == old_id: - quality_changes_index = global_stack.getContainerIndex(old_container) - global_stack.replaceContainer(quality_changes_index, container) - continue - - for stack in extruder_stacks: - old_container = stack.findContainer({"type": "quality_changes"}) + # Replace the quality/definition changes container + if container_type == "quality_changes": + old_container = global_stack.qualityChanges + elif container_type == "definition_changes": + old_container = global_stack.definitionChanges + # old_container = global_stack.findContainer({"type": container_type}) if old_container.getId() == old_id: - quality_changes_index = stack.getContainerIndex(old_container) - stack.replaceContainer(quality_changes_index, container) + changes_index = global_stack.getContainerIndex(old_container) + global_stack.replaceContainer(changes_index, container) + continue + + for stack in extruder_stacks: + old_container = stack.findContainer({"type": container_type}) + if old_container.getId() == old_id: + changes_index = stack.getContainerIndex(old_container) + stack.replaceContainer(changes_index, container) if self._resolve_strategies["material"] == "new": for material in material_containers: @@ -455,24 +617,26 @@ class ThreeMFWorkspaceReader(WorkspaceReader): stack.replaceContainer(material_index, material) continue - for stack in extruder_stacks: - ExtruderManager.getInstance().registerExtruder(stack, global_stack.getId()) + if extruder_stacks: + for stack in extruder_stacks: + ExtruderManager.getInstance().registerExtruder(stack, global_stack.getId()) else: # Machine has no extruders, but it needs to be registered with the extruder manager. ExtruderManager.getInstance().registerExtruder(None, global_stack.getId()) Logger.log("d", "Workspace loading is notifying rest of the code of changes...") - # Notify everything/one that is to notify about changes. - global_stack.containersChanged.emit(global_stack.getTop()) - - for stack in extruder_stacks: - stack.setNextStack(global_stack) - stack.containersChanged.emit(stack.getTop()) + if self._resolve_strategies["machine"] == "new": + for stack in extruder_stacks: + stack.setNextStack(global_stack) + stack.containersChanged.emit(stack.getTop()) # Actually change the active machine. Application.getInstance().setGlobalContainerStack(global_stack) + # Notify everything/one that is to notify about changes. + global_stack.containersChanged.emit(global_stack.getTop()) + # Load all the nodes / meshdata of the workspace nodes = self._3mf_mesh_reader.read(file_name) if nodes is None: diff --git a/plugins/3MFReader/WorkspaceDialog.py b/plugins/3MFReader/WorkspaceDialog.py index 1bae9575f2..9d6c70cf8b 100644 --- a/plugins/3MFReader/WorkspaceDialog.py +++ b/plugins/3MFReader/WorkspaceDialog.py @@ -1,7 +1,7 @@ # Copyright (c) 2016 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. -from PyQt5.QtCore import Qt, QUrl, pyqtSignal, QObject, pyqtProperty, QCoreApplication +from PyQt5.QtCore import QUrl, pyqtSignal, QObject, pyqtProperty, QCoreApplication from UM.FlameProfiler import pyqtSlot from PyQt5.QtQml import QQmlComponent, QQmlContext from UM.PluginRegistry import PluginRegistry @@ -29,11 +29,13 @@ class WorkspaceDialog(QObject): self._default_strategy = "override" self._result = {"machine": self._default_strategy, "quality_changes": self._default_strategy, + "definition_changes": self._default_strategy, "material": self._default_strategy} self._visible = False self.showDialogSignal.connect(self.__show) self._has_quality_changes_conflict = False + self._has_definition_changes_conflict = False self._has_machine_conflict = False self._has_material_conflict = False self._num_visible_settings = 0 @@ -51,6 +53,7 @@ class WorkspaceDialog(QObject): machineConflictChanged = pyqtSignal() qualityChangesConflictChanged = pyqtSignal() + definitionChangesConflictChanged = pyqtSignal() materialConflictChanged = pyqtSignal() numVisibleSettingsChanged = pyqtSignal() activeModeChanged = pyqtSignal() @@ -185,6 +188,10 @@ class WorkspaceDialog(QObject): def qualityChangesConflict(self): return self._has_quality_changes_conflict + @pyqtProperty(bool, notify=definitionChangesConflictChanged) + def definitionChangesConflict(self): + return self._has_definition_changes_conflict + @pyqtProperty(bool, notify=materialConflictChanged) def materialConflict(self): return self._has_material_conflict @@ -214,11 +221,18 @@ class WorkspaceDialog(QObject): self._has_quality_changes_conflict = quality_changes_conflict self.qualityChangesConflictChanged.emit() + def setDefinitionChangesConflict(self, definition_changes_conflict): + if self._has_definition_changes_conflict != definition_changes_conflict: + self._has_definition_changes_conflict = definition_changes_conflict + self.definitionChangesConflictChanged.emit() + def getResult(self): if "machine" in self._result and not self._has_machine_conflict: self._result["machine"] = None if "quality_changes" in self._result and not self._has_quality_changes_conflict: self._result["quality_changes"] = None + if "definition_changes" in self._result and not self._has_definition_changes_conflict: + self._result["definition_changes"] = None if "material" in self._result and not self._has_material_conflict: self._result["material"] = None return self._result @@ -240,6 +254,7 @@ class WorkspaceDialog(QObject): # Reset the result self._result = {"machine": self._default_strategy, "quality_changes": self._default_strategy, + "definition_changes": self._default_strategy, "material": self._default_strategy} self._visible = True self.showDialogSignal.emit() diff --git a/plugins/CuraEngineBackend/Cura.proto b/plugins/CuraEngineBackend/Cura.proto index 4eab500f0a..c2e4e5bb5f 100644 --- a/plugins/CuraEngineBackend/Cura.proto +++ b/plugins/CuraEngineBackend/Cura.proto @@ -90,9 +90,21 @@ message GCodeLayer { } -message PrintTimeMaterialEstimates { // The print time for the whole print and material estimates for the extruder - float time = 1; // Total time estimate - repeated MaterialEstimates materialEstimates = 2; // materialEstimates data +message PrintTimeMaterialEstimates { // The print time for each feature and material estimates for the extruder + // Time estimate in each feature + float time_none = 1; + float time_inset_0 = 2; + float time_inset_x = 3; + float time_skin = 4; + float time_support = 5; + float time_skirt = 6; + float time_infill = 7; + float time_support_infill = 8; + float time_travel = 9; + float time_retract = 10; + float time_support_interface = 11; + + repeated MaterialEstimates materialEstimates = 12; // materialEstimates data } message MaterialEstimates { @@ -121,4 +133,4 @@ message GCodePrefix { } message SlicingFinished { -} \ No newline at end of file +} diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index 981145bebd..9c9c9a1b90 100755 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -13,9 +13,9 @@ from UM.Resources import Resources from UM.Settings.Validator import ValidatorState #To find if a setting is in an error state. We can't slice then. from UM.Platform import Platform from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator +from UM.Qt.Duration import DurationFormat from PyQt5.QtCore import QObject, pyqtSlot - from cura.Settings.ExtruderManager import ExtruderManager from . import ProcessSlicedLayersJob from . import StartSliceJob @@ -187,7 +187,19 @@ class CuraEngineBackend(QObject, Backend): Logger.log("w", "Slice unnecessary, nothing has changed that needs reslicing.") return - self.printDurationMessage.emit(0, [0]) + self.printDurationMessage.emit({ + "none": 0, + "inset_0": 0, + "inset_x": 0, + "skin": 0, + "support": 0, + "skirt": 0, + "infill": 0, + "support_infill": 0, + "travel": 0, + "retract": 0, + "support_interface": 0 + }, [0]) self._stored_layer_data = [] self._stored_optimized_layer_data = [] @@ -273,9 +285,15 @@ class CuraEngineBackend(QObject, Backend): if not extruders: error_keys = self._global_container_stack.getErrorKeys() error_labels = set() - definition_container = self._global_container_stack.getBottom() for key in error_keys: - error_labels.add(definition_container.findDefinitions(key = key)[0].label) + for stack in [self._global_container_stack] + extruders: #Search all container stacks for the definition of this setting. Some are only in an extruder stack. + definitions = stack.getBottom().findDefinitions(key = key) + if definitions: + break #Found it! No need to continue search. + else: #No stack has a definition for this setting. + Logger.log("w", "When checking settings for errors, unable to find definition for key: {key}".format(key = key)) + continue + error_labels.add(definitions[0].label) error_labels = ", ".join(error_labels) self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice with the current settings. The following settings have errors: {0}".format(error_labels))) @@ -442,6 +460,15 @@ class CuraEngineBackend(QObject, Backend): self.backendStateChange.emit(BackendState.Done) self.processingProgress.emit(1.0) + for line in self._scene.gcode_list: + replaced = line.replace("{print_time}", str(Application.getInstance().getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601))) + replaced = replaced.replace("{filament_amount}", str(Application.getInstance().getPrintInformation().materialLengths)) + replaced = replaced.replace("{filament_weight}", str(Application.getInstance().getPrintInformation().materialWeights)) + replaced = replaced.replace("{filament_cost}", str(Application.getInstance().getPrintInformation().materialCosts)) + replaced = replaced.replace("{jobname}", str(Application.getInstance().getPrintInformation().jobName)) + + self._scene.gcode_list[self._scene.gcode_list.index(line)] = replaced + self._slicing = False self._need_slicing = False Logger.log("d", "Slicing took %s seconds", time() - self._slice_start_time ) @@ -466,13 +493,26 @@ class CuraEngineBackend(QObject, Backend): ## Called when a print time message is received from the engine. # - # \param message The protobuff message containing the print time and + # \param message The protobuf message containing the print time per feature and # material amount per extruder def _onPrintTimeMaterialEstimates(self, message): material_amounts = [] for index in range(message.repeatedMessageCount("materialEstimates")): material_amounts.append(message.getRepeatedMessage("materialEstimates", index).material_amount) - self.printDurationMessage.emit(message.time, material_amounts) + feature_times = { + "none": message.time_none, + "inset_0": message.time_inset_0, + "inset_x": message.time_inset_x, + "skin": message.time_skin, + "support": message.time_support, + "skirt": message.time_skirt, + "infill": message.time_infill, + "support_infill": message.time_support_infill, + "travel": message.time_travel, + "retract": message.time_retract, + "support_interface": message.time_support_interface + } + self.printDurationMessage.emit(feature_times, material_amounts) ## Creates a new socket connection. def _createSocket(self): diff --git a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py index 0d706f59b8..f7be2edc04 100644 --- a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py +++ b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py @@ -179,9 +179,10 @@ class ProcessSlicedLayersJob(Job): # Single extruder via global stack. material_color_map = numpy.zeros((1, 4), dtype=numpy.float32) material = global_container_stack.findContainer({"type": "material"}) - color_code = material.getMetaDataEntry("color_code") - if color_code is None: # not all stacks have a material color - color_code = "#e0e000" + color_code = "#e0e000" + if material: + if material.getMetaDataEntry("color_code") is not None: + color_code = material.getMetaDataEntry("color_code") color = colorCodeToRGBA(color_code) material_color_map[0, :] = color diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index c8cbbe8040..8abb72fa92 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -4,6 +4,7 @@ import numpy from string import Formatter from enum import IntEnum +import time from UM.Job import Job from UM.Application import Application @@ -230,25 +231,22 @@ class StartSliceJob(Job): keys = stack.getAllKeys() settings = {} for key in keys: - # Use resolvement value if available, or take the value - resolved_value = stack.getProperty(key, "resolve") - if resolved_value is not None: - # There is a resolvement value. Check if we need to use it. - user_container = stack.findContainer({"type": "user"}) - quality_changes_container = stack.findContainer({"type": "quality_changes"}) - if user_container.hasProperty(key,"value") or quality_changes_container.hasProperty(key,"value"): - # Normal case - settings[key] = stack.getProperty(key, "value") - else: - settings[key] = resolved_value - else: - # Normal case - settings[key] = stack.getProperty(key, "value") + settings[key] = stack.getProperty(key, "value") Job.yieldThread() start_gcode = settings["machine_start_gcode"] - settings["material_bed_temp_prepend"] = "{material_bed_temperature}" not in start_gcode #Pre-compute material material_bed_temp_prepend and material_print_temp_prepend - settings["material_print_temp_prepend"] = "{material_print_temperature}" not in start_gcode + #Pre-compute material material_bed_temp_prepend and material_print_temp_prepend + bed_temperature_settings = {"material_bed_temperature", "material_bed_temperature_layer_0"} + settings["material_bed_temp_prepend"] = all(("{" + setting + "}" not in start_gcode for setting in bed_temperature_settings)) + print_temperature_settings = {"material_print_temperature", "material_print_temperature_layer_0", "default_material_print_temperature", "material_initial_print_temperature", "material_final_print_temperature", "material_standby_temperature"} + settings["material_print_temp_prepend"] = all(("{" + setting + "}" not in start_gcode for setting in print_temperature_settings)) + + settings["print_bed_temperature"] = settings["material_bed_temperature"] + settings["print_temperature"] = settings["material_print_temperature"] + + settings["time"] = time.strftime('%H:%M:%S') + settings["date"] = time.strftime('%d-%m-%Y') + settings["day"] = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][int(time.strftime('%w'))] for key, value in settings.items(): #Add all submessages for each individual setting. setting_message = self._slice_message.getMessage("global_settings").addRepeatedMessage("settings") diff --git a/plugins/GCodeProfileReader/GCodeProfileReader.py b/plugins/GCodeProfileReader/GCodeProfileReader.py index abfef6e296..0abbcfc833 100644 --- a/plugins/GCodeProfileReader/GCodeProfileReader.py +++ b/plugins/GCodeProfileReader/GCodeProfileReader.py @@ -56,7 +56,7 @@ class GCodeProfileReader(ProfileReader): # TODO: Consider moving settings to the start? serialized = "" # Will be filled with the serialized profile. try: - with open(file_name) as f: + with open(file_name, "r") as f: for line in f: if line.startswith(prefix): # Remove the prefix and the newline from the line and add it to the rest. @@ -66,9 +66,13 @@ class GCodeProfileReader(ProfileReader): return None serialized = unescapeGcodeComment(serialized) - Logger.log("i", "Serialized the following from %s: %s" %(file_name, repr(serialized))) - json_data = json.loads(serialized) + # serialized data can be invalid JSON + try: + json_data = json.loads(serialized) + except Exception as e: + Logger.log("e", "Could not parse serialized JSON data from GCode %s, error: %s", file_name, e) + return None profiles = [] global_profile = readQualityProfileFromString(json_data["global_quality"]) diff --git a/plugins/GCodeWriter/GCodeWriter.py b/plugins/GCodeWriter/GCodeWriter.py index 162738f073..ad730fbe2a 100644 --- a/plugins/GCodeWriter/GCodeWriter.py +++ b/plugins/GCodeWriter/GCodeWriter.py @@ -100,7 +100,7 @@ class GCodeWriter(MeshWriter): prefix = ";SETTING_" + str(GCodeWriter.version) + " " # The prefix to put before each line. prefix_length = len(prefix) - container_with_profile = stack.findContainer({"type": "quality_changes"}) + container_with_profile = stack.qualityChanges if not container_with_profile: Logger.log("e", "No valid quality profile found, not writing settings to GCode!") return "" @@ -115,7 +115,7 @@ class GCodeWriter(MeshWriter): data = {"global_quality": serialized} for extruder in sorted(ExtruderManager.getInstance().getMachineExtruders(stack.getId()), key = lambda k: k.getMetaDataEntry("position")): - extruder_quality = extruder.findContainer({"type": "quality_changes"}) + extruder_quality = extruder.qualityChanges if not extruder_quality: Logger.log("w", "No extruder quality profile found, not writing quality for extruder %s to file!", extruder.getId()) continue diff --git a/plugins/LayerView/LayerView.qml b/plugins/LayerView/LayerView.qml index 3078b278df..9dc038fe70 100755 --- a/plugins/LayerView/LayerView.qml +++ b/plugins/LayerView/LayerView.qml @@ -43,7 +43,8 @@ Item property bool show_helpers: UM.Preferences.getValue("layerview/show_helpers") property bool show_skin: UM.Preferences.getValue("layerview/show_skin") property bool show_infill: UM.Preferences.getValue("layerview/show_infill") - property bool show_legend: UM.LayerView.compatibilityMode || UM.Preferences.getValue("layerview/layer_view_type") == 1 + // if we are in compatibility mode, we only show the "line type" + property bool show_legend: UM.LayerView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type") == 1 property bool only_show_top_layers: UM.Preferences.getValue("view/only_show_top_layers") property int top_layer_count: UM.Preferences.getValue("view/top_layer_count") @@ -107,27 +108,23 @@ Item visible: !UM.LayerView.compatibilityMode style: UM.Theme.styles.combobox - property int layer_view_type: UM.Preferences.getValue("layerview/layer_view_type") - currentIndex: layer_view_type // index matches type_id - onActivated: { - // Combobox selection - var type_id = index; - UM.Preferences.setValue("layerview/layer_view_type", type_id); - updateLegend(type_id); - } - onModelChanged: { - updateLegend(UM.Preferences.getValue("layerview/layer_view_type")); + onActivated: + { + UM.Preferences.setValue("layerview/layer_view_type", index); } - // Update visibility of legend. - function updateLegend(type_id) { - if (UM.LayerView.compatibilityMode || (type_id == 1)) { - // Line type - view_settings.show_legend = true; - } else { - view_settings.show_legend = false; - } + Component.onCompleted: + { + currentIndex = UM.LayerView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type"); + updateLegends(currentIndex); } + + function updateLegends(type_id) + { + // update visibility of legends + view_settings.show_legend = UM.LayerView.compatibilityMode || (type_id == 1); + } + } Label @@ -153,7 +150,8 @@ Item target: UM.Preferences onPreferenceChanged: { - layerTypeCombobox.layer_view_type = UM.Preferences.getValue("layerview/layer_view_type"); + layerTypeCombobox.currentIndex = UM.LayerView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type"); + layerTypeCombobox.updateLegends(layerTypeCombobox.currentIndex); view_settings.extruder_opacities = UM.Preferences.getValue("layerview/extruder_opacities").split("|"); view_settings.show_travel_moves = UM.Preferences.getValue("layerview/show_travel_moves"); view_settings.show_helpers = UM.Preferences.getValue("layerview/show_helpers"); @@ -273,17 +271,18 @@ Item { typesLegenModelNoCheck.append({ label: catalog.i18nc("@label", "Top / Bottom"), - colorId: "layerview_skin" + colorId: "layerview_skin", }); typesLegenModelNoCheck.append({ label: catalog.i18nc("@label", "Inner Wall"), - colorId: "layerview_inset_x" + colorId: "layerview_inset_x", }); } } Label { text: label + visible: view_settings.show_legend Rectangle { anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right diff --git a/plugins/MachineSettingsAction/MachineSettingsAction.py b/plugins/MachineSettingsAction/MachineSettingsAction.py old mode 100644 new mode 100755 index c27f8db4a6..bfade90f2b --- a/plugins/MachineSettingsAction/MachineSettingsAction.py +++ b/plugins/MachineSettingsAction/MachineSettingsAction.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016 Ultimaker B.V. +# Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. from PyQt5.QtCore import pyqtProperty, pyqtSignal @@ -7,12 +7,15 @@ from UM.FlameProfiler import pyqtSlot from cura.MachineAction import MachineAction from UM.Application import Application +from UM.Preferences import Preferences from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.DefinitionContainer import DefinitionContainer +from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Logger import Logger from cura.Settings.CuraContainerRegistry import CuraContainerRegistry +from cura.Settings.ExtruderManager import ExtruderManager import UM.i18n catalog = UM.i18n.i18nCatalog("cura") @@ -25,36 +28,80 @@ class MachineSettingsAction(MachineAction): super().__init__("MachineSettingsAction", catalog.i18nc("@action", "Machine Settings")) self._qml_url = "MachineSettingsAction.qml" + self._global_container_stack = None self._container_index = 0 + self._extruder_container_index = 0 self._container_registry = ContainerRegistry.getInstance() self._container_registry.containerAdded.connect(self._onContainerAdded) + self._container_registry.containerRemoved.connect(self._onContainerRemoved) + Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged) + ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged) + + self._backend = Application.getInstance().getBackend() + + def _onContainerAdded(self, container): + # Add this action as a supported action to all machine definitions + if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine": + Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey()) + + def _onContainerRemoved(self, container): + # Remove definition_changes containers when a stack is removed + if container.getMetaDataEntry("type") in ["machine", "extruder_train"]: + definition_changes_container = container.findContainer({"type": "definition_changes"}) + if not definition_changes_container: + return + + self._container_registry.removeContainer(definition_changes_container.getId()) def _reset(self): - global_container_stack = Application.getInstance().getGlobalContainerStack() - if not global_container_stack: + if not self._global_container_stack: return # Make sure there is a definition_changes container to store the machine settings - definition_changes_container = global_container_stack.findContainer({"type": "definition_changes"}) + definition_changes_container = self._global_container_stack.findContainer({"type": "definition_changes"}) if not definition_changes_container: - definition_changes_container = self._createDefinitionChangesContainer(global_container_stack) + definition_changes_container = self._createDefinitionChangesContainer(self._global_container_stack, self._global_container_stack.getName() + "_settings") # Notify the UI in which container to store the machine settings data - container_index = global_container_stack.getContainerIndex(definition_changes_container) + container_index = self._global_container_stack.getContainerIndex(definition_changes_container) if container_index != self._container_index: self._container_index = container_index self.containerIndexChanged.emit() - def _createDefinitionChangesContainer(self, global_container_stack, container_index = None): - definition_changes_container = InstanceContainer(global_container_stack.getName() + "_settings") - definition = global_container_stack.getBottom() + # Disable autoslicing while the machineaction is showing + self._backend.disableTimer() + + @pyqtSlot() + def onFinishAction(self): + # Restore autoslicing when the machineaction is dismissed + if self._backend.determineAutoSlicing(): + self._backend.tickle() + + def _onActiveExtruderStackChanged(self): + extruder_container_stack = ExtruderManager.getInstance().getActiveExtruderStack() + if not self._global_container_stack or not extruder_container_stack: + return + + # Make sure there is a definition_changes container to store the machine settings + definition_changes_container = extruder_container_stack.findContainer({"type": "definition_changes"}) + if not definition_changes_container: + definition_changes_container = self._createDefinitionChangesContainer(extruder_container_stack, extruder_container_stack.getId() + "_settings") + + # Notify the UI in which container to store the machine settings data + container_index = extruder_container_stack.getContainerIndex(definition_changes_container) + if container_index != self._extruder_container_index: + self._extruder_container_index = container_index + self.extruderContainerIndexChanged.emit() + + def _createDefinitionChangesContainer(self, container_stack, container_name, container_index = None): + definition_changes_container = InstanceContainer(container_name) + definition = container_stack.getBottom() definition_changes_container.setDefinition(definition) definition_changes_container.addMetaDataEntry("type", "definition_changes") self._container_registry.addContainer(definition_changes_container) - # Insert definition_changes between the definition and the variant - global_container_stack.insertContainer(-1, definition_changes_container) + container_stack.definitionChanges = definition_changes_container return definition_changes_container @@ -64,15 +111,129 @@ class MachineSettingsAction(MachineAction): def containerIndex(self): return self._container_index - def _onContainerAdded(self, container): - # Add this action as a supported action to all machine definitions - if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine": - if container.getProperty("machine_extruder_count", "value") > 1: - # Multiextruder printers are not currently supported - Logger.log("d", "Not attaching MachineSettingsAction to %s; Multi-extrusion printers are not supported", container.getId()) - return + extruderContainerIndexChanged = pyqtSignal() + + @pyqtProperty(int, notify = extruderContainerIndexChanged) + def extruderContainerIndex(self): + return self._extruder_container_index + + + def _onGlobalContainerChanged(self): + self._global_container_stack = Application.getInstance().getGlobalContainerStack() + + # This additional emit is needed because we cannot connect a UM.Signal directly to a pyqtSignal + self.globalContainerChanged.emit() + + globalContainerChanged = pyqtSignal() + + @pyqtProperty(int, notify = globalContainerChanged) + def definedExtruderCount(self): + if not self._global_container_stack: + return 0 + + return len(self._global_container_stack.getMetaDataEntry("machine_extruder_trains")) + + @pyqtSlot(int) + def setMachineExtruderCount(self, extruder_count): + machine_manager = Application.getInstance().getMachineManager() + extruder_manager = ExtruderManager.getInstance() + + definition_changes_container = self._global_container_stack.findContainer({"type": "definition_changes"}) + if not self._global_container_stack or not definition_changes_container: + return + + previous_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value") + if extruder_count == previous_extruder_count: + return + + extruder_material_id = None + extruder_variant_id = None + if extruder_count == 1: + # Get the material and variant of the first extruder before setting the number extruders to 1 + if machine_manager.hasMaterials: + extruder_material_id = machine_manager.allActiveMaterialIds[extruder_manager.extruderIds["0"]] + if machine_manager.hasVariants: + extruder_variant_id = machine_manager.allActiveVariantIds[extruder_manager.extruderIds["0"]] + + # Copy any settable_per_extruder setting value from the extruders to the global stack + extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks() + extruder_stacks.reverse() # make sure the first extruder is done last, so its settings override any higher extruder settings + + global_user_container = self._global_container_stack.getTop() + for extruder_stack in extruder_stacks: + extruder_index = extruder_stack.getMetaDataEntry("position") + extruder_user_container = extruder_stack.getTop() + for setting_instance in extruder_user_container.findInstances(): + setting_key = setting_instance.definition.key + settable_per_extruder = self._global_container_stack.getProperty(setting_key, "settable_per_extruder") + if settable_per_extruder: + limit_to_extruder = self._global_container_stack.getProperty(setting_key, "limit_to_extruder") + + if limit_to_extruder == "-1" or limit_to_extruder == extruder_index: + global_user_container.setProperty(setting_key, "value", extruder_user_container.getProperty(setting_key, "value")) + extruder_user_container.removeInstance(setting_key) + + # Check to see if any features are set to print with an extruder that will no longer exist + for setting_key in ["adhesion_extruder_nr", "support_extruder_nr", "support_extruder_nr_layer_0", "support_infill_extruder_nr", "support_interface_extruder_nr"]: + if int(self._global_container_stack.getProperty(setting_key, "value")) > extruder_count - 1: + Logger.log("i", "Lowering %s setting to match number of extruders", setting_key) + self._global_container_stack.getTop().setProperty(setting_key, "value", extruder_count - 1) + + # Check to see if any objects are set to print with an extruder that will no longer exist + root_node = Application.getInstance().getController().getScene().getRoot() + for node in DepthFirstIterator(root_node): + if node.getMeshData(): + extruder_nr = node.callDecoration("getActiveExtruderPosition") + + if extruder_nr is not None and int(extruder_nr) > extruder_count - 1: + node.callDecoration("setActiveExtruder", extruder_manager.getExtruderStack(extruder_count - 1).getId()) + + definition_changes_container.setProperty("machine_extruder_count", "value", extruder_count) + self.forceUpdate() + + if extruder_count > 1: + # Multiextrusion + + # Make sure one of the extruder stacks is active + if extruder_manager.activeExtruderIndex == -1: + extruder_manager.setActiveExtruderIndex(0) + + # Move settable_per_extruder values out of the global container + if previous_extruder_count == 1: + extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks() + global_user_container = self._global_container_stack.getTop() + + for setting_instance in global_user_container.findInstances(): + setting_key = setting_instance.definition.key + settable_per_extruder = self._global_container_stack.getProperty(setting_key, "settable_per_extruder") + if settable_per_extruder: + limit_to_extruder = int(self._global_container_stack.getProperty(setting_key, "limit_to_extruder")) + extruder_stack = extruder_stacks[max(0, limit_to_extruder)] + extruder_stack.getTop().setProperty(setting_key, "value", global_user_container.getProperty(setting_key, "value")) + global_user_container.removeInstance(setting_key) + else: + # Single extrusion + + # Make sure the machine stack is active + if extruder_manager.activeExtruderIndex > -1: + extruder_manager.setActiveExtruderIndex(-1) + + # Restore material and variant on global stack + # MachineManager._onGlobalContainerChanged removes the global material and variant of multiextruder machines + if extruder_material_id or extruder_variant_id: + # Prevent the DiscardOrKeepProfileChangesDialog from popping up (twice) if there are user changes + # The dialog is not relevant here, since we're restoring the previous situation as good as possible + preferences = Preferences.getInstance() + choice_on_profile_override = preferences.getValue("cura/choice_on_profile_override") + preferences.setValue("cura/choice_on_profile_override", "always_keep") + + if extruder_material_id: + machine_manager.setActiveMaterial(extruder_material_id) + if extruder_variant_id: + machine_manager.setActiveVariant(extruder_variant_id) + + preferences.setValue("cura/choice_on_profile_override", choice_on_profile_override) - Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey()) @pyqtSlot() def forceUpdate(self): @@ -83,34 +244,34 @@ class MachineSettingsAction(MachineAction): @pyqtSlot() def updateHasMaterialsMetadata(self): # Updates the has_materials metadata flag after switching gcode flavor - global_container_stack = Application.getInstance().getGlobalContainerStack() - if global_container_stack: - definition = global_container_stack.getBottom() - if definition.getProperty("machine_gcode_flavor", "value") == "UltiGCode" and not definition.getMetaDataEntry("has_materials", False): - has_materials = global_container_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode" + if not self._global_container_stack: + return - material_container = global_container_stack.findContainer({"type": "material"}) - material_index = global_container_stack.getContainerIndex(material_container) + definition = self._global_container_stack.getBottom() + if definition.getProperty("machine_gcode_flavor", "value") == "UltiGCode" and not definition.getMetaDataEntry("has_materials", False): + has_materials = self._global_container_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode" - if has_materials: - if "has_materials" in global_container_stack.getMetaData(): - global_container_stack.setMetaDataEntry("has_materials", True) - else: - global_container_stack.addMetaDataEntry("has_materials", True) + material_container = self._global_container_stack.material + material_index = self._global_container_stack.getContainerIndex(material_container) - # Set the material container to a sane default - if material_container.getId() == "empty_material": - search_criteria = { "type": "material", "definition": "fdmprinter", "id": "*pla*" } - containers = self._container_registry.findInstanceContainers(**search_criteria) - if containers: - global_container_stack.replaceContainer(material_index, containers[0]) + if has_materials: + if "has_materials" in self._global_container_stack.getMetaData(): + self._global_container_stack.setMetaDataEntry("has_materials", True) else: - # The metadata entry is stored in an ini, and ini files are parsed as strings only. - # Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False. - if "has_materials" in global_container_stack.getMetaData(): - global_container_stack.removeMetaDataEntry("has_materials") + self._global_container_stack.addMetaDataEntry("has_materials", True) - empty_material = self._container_registry.findInstanceContainers(id = "empty_material")[0] - global_container_stack.replaceContainer(material_index, empty_material) + # Set the material container to a sane default + if material_container.getId() == "empty_material": + search_criteria = { "type": "material", "definition": "fdmprinter", "id": "*pla*"} + containers = self._container_registry.findInstanceContainers(**search_criteria) + if containers: + self._global_container_stack.replaceContainer(material_index, containers[0]) + else: + # The metadata entry is stored in an ini, and ini files are parsed as strings only. + # Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False. + if "has_materials" in self._global_container_stack.getMetaData(): + self._global_container_stack.removeMetaDataEntry("has_materials") - Application.getInstance().globalContainerStackChanged.emit() + self._global_container_stack.material = ContainerRegistry.getInstance().getEmptyInstanceContainer() + + Application.getInstance().globalContainerStackChanged.emit() diff --git a/plugins/MachineSettingsAction/MachineSettingsAction.qml b/plugins/MachineSettingsAction/MachineSettingsAction.qml index 26bbccd44a..654030b013 100644 --- a/plugins/MachineSettingsAction/MachineSettingsAction.qml +++ b/plugins/MachineSettingsAction/MachineSettingsAction.qml @@ -12,6 +12,32 @@ import Cura 1.0 as Cura Cura.MachineAction { + id: base + property var extrudersModel: Cura.ExtrudersModel{} + property int extruderTabsCount: 0 + + Connections + { + target: base.extrudersModel + onModelChanged: + { + var extruderCount = base.extrudersModel.rowCount(); + base.extruderTabsCount = extruderCount > 1 ? extruderCount : 0; + } + } + + Connections + { + target: dialog ? dialog : null + ignoreUnknownSignals: true + // Any which way this action dialog is dismissed, make sure it is properly finished + onNextClicked: manager.onFinishAction() + onBackClicked: manager.onFinishAction() + onAccepted: manager.onFinishAction() + onRejected: manager.onFinishAction() + onClosing: manager.onFinishAction() + } + anchors.fill: parent; Item { @@ -28,428 +54,638 @@ Cura.MachineAction wrapMode: Text.WordWrap font.pointSize: 18; } - Label + + TabView { - id: pageDescription + id: settingsTabs + height: parent.height - y + width: parent.width + anchors.left: parent.left anchors.top: pageTitle.bottom anchors.topMargin: UM.Theme.getSize("default_margin").height - width: parent.width - wrapMode: Text.WordWrap - text: catalog.i18nc("@label", "Please enter the correct settings for your printer below:") - } - Column - { - height: parent.height - y - width: parent.width - UM.Theme.getSize("default_margin").width - spacing: UM.Theme.getSize("default_margin").height + property real columnWidth: Math.floor((width - 3 * UM.Theme.getSize("default_margin").width) / 2) - anchors.left: parent.left - anchors.top: pageDescription.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height - - Row + Tab { - width: parent.width - spacing: UM.Theme.getSize("default_margin").height + title: catalog.i18nc("@title:tab", "Printer"); + anchors.margins: UM.Theme.getSize("default_margin").width Column { - width: parent.width / 2 spacing: UM.Theme.getSize("default_margin").height - Label + Row { - text: catalog.i18nc("@label", "Printer Settings") - font.bold: true - } + width: parent.width + spacing: UM.Theme.getSize("default_margin").height - Grid - { - columns: 3 - columnSpacing: UM.Theme.getSize("default_margin").width - - Label + Column { - text: catalog.i18nc("@label", "X (Width)") - } - TextField - { - id: buildAreaWidthField - text: machineWidthProvider.properties.value - validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ } - onEditingFinished: { machineWidthProvider.setPropertyValue("value", text); manager.forceUpdate() } - } - Label - { - text: catalog.i18nc("@label", "mm") - } - - Label - { - text: catalog.i18nc("@label", "Y (Depth)") - } - TextField - { - id: buildAreaDepthField - text: machineDepthProvider.properties.value - validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ } - onEditingFinished: { machineDepthProvider.setPropertyValue("value", text); manager.forceUpdate() } - } - Label - { - text: catalog.i18nc("@label", "mm") - } - - Label - { - text: catalog.i18nc("@label", "Z (Height)") - } - TextField - { - id: buildAreaHeightField - text: machineHeightProvider.properties.value - validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ } - onEditingFinished: { machineHeightProvider.setPropertyValue("value", text); manager.forceUpdate() } - } - Label - { - text: catalog.i18nc("@label", "mm") - } - } - - Column - { - Row - { - spacing: UM.Theme.getSize("default_margin").width + width: settingsTabs.columnWidth + spacing: UM.Theme.getSize("default_margin").height Label { - text: catalog.i18nc("@label", "Build Plate Shape") + text: catalog.i18nc("@label", "Printer Settings") + font.bold: true } - ComboBox + Grid { - id: shapeComboBox - model: ListModel + columns: 2 + columnSpacing: UM.Theme.getSize("default_margin").width + rowSpacing: UM.Theme.getSize("default_lining").width + + Label { - id: shapesModel - Component.onCompleted: + text: catalog.i18nc("@label", "X (Width)") + } + Loader + { + id: buildAreaWidthField + sourceComponent: numericTextFieldWithUnit + property var propertyProvider: machineWidthProvider + property string unit: catalog.i18nc("@label", "mm") + property bool forceUpdateOnChange: true + } + + Label + { + text: catalog.i18nc("@label", "Y (Depth)") + } + Loader + { + id: buildAreaDepthField + sourceComponent: numericTextFieldWithUnit + property var propertyProvider: machineDepthProvider + property string unit: catalog.i18nc("@label", "mm") + property bool forceUpdateOnChange: true + } + + Label + { + text: catalog.i18nc("@label", "Z (Height)") + } + Loader + { + id: buildAreaHeightField + sourceComponent: numericTextFieldWithUnit + property var propertyProvider: machineHeightProvider + property string unit: catalog.i18nc("@label", "mm") + property bool forceUpdateOnChange: true + } + } + + Column + { + Row + { + spacing: UM.Theme.getSize("default_margin").width + + Label { - // Options come in as a string-representation of an OrderedDict - var options = machineShapeProvider.properties.options.match(/^OrderedDict\(\[\((.*)\)\]\)$/); - if(options) + text: catalog.i18nc("@label", "Build Plate Shape") + } + + ComboBox + { + id: shapeComboBox + model: ListModel { - options = options[1].split("), (") - for(var i = 0; i < options.length; i++) + id: shapesModel + Component.onCompleted: { - var option = options[i].substring(1, options[i].length - 1).split("', '") - shapesModel.append({text: option[1], value: option[0]}); + // Options come in as a string-representation of an OrderedDict + var options = machineShapeProvider.properties.options.match(/^OrderedDict\(\[\((.*)\)\]\)$/); + if(options) + { + options = options[1].split("), (") + for(var i = 0; i < options.length; i++) + { + var option = options[i].substring(1, options[i].length - 1).split("', '") + shapesModel.append({text: option[1], value: option[0]}); + } + } + } + } + currentIndex: + { + var currentValue = machineShapeProvider.properties.value; + var index = 0; + for(var i = 0; i < shapesModel.count; i++) + { + if(shapesModel.get(i).value == currentValue) { + index = i; + break; + } + } + return index + } + onActivated: + { + if(machineShapeProvider.properties.value != shapesModel.get(index).value) + { + machineShapeProvider.setPropertyValue("value", shapesModel.get(index).value); + manager.forceUpdate(); } } } } - currentIndex: + CheckBox { - var currentValue = machineShapeProvider.properties.value; - var index = 0; - for(var i = 0; i < shapesModel.count; i++) + id: centerIsZeroCheckBox + text: catalog.i18nc("@option:check", "Machine Center is Zero") + checked: String(machineCenterIsZeroProvider.properties.value).toLowerCase() != 'false' + onClicked: { - if(shapesModel.get(i).value == currentValue) { - index = i; - break; + machineCenterIsZeroProvider.setPropertyValue("value", checked); + manager.forceUpdate(); + } + } + CheckBox + { + id: heatedBedCheckBox + text: catalog.i18nc("@option:check", "Heated Bed") + checked: String(machineHeatedBedProvider.properties.value).toLowerCase() != 'false' + onClicked: machineHeatedBedProvider.setPropertyValue("value", checked) + } + } + + Row + { + spacing: UM.Theme.getSize("default_margin").width + + Label + { + text: catalog.i18nc("@label", "GCode Flavor") + } + + ComboBox + { + model: ListModel + { + id: flavorModel + Component.onCompleted: + { + // Options come in as a string-representation of an OrderedDict + var options = machineGCodeFlavorProvider.properties.options.match(/^OrderedDict\(\[\((.*)\)\]\)$/); + if(options) + { + options = options[1].split("), (") + for(var i = 0; i < options.length; i++) + { + var option = options[i].substring(1, options[i].length - 1).split("', '") + flavorModel.append({text: option[1], value: option[0]}); + } + } } } - return index - } - onActivated: - { - machineShapeProvider.setPropertyValue("value", shapesModel.get(index).value); - manager.forceUpdate(); + currentIndex: + { + var currentValue = machineGCodeFlavorProvider.properties.value; + var index = 0; + for(var i = 0; i < flavorModel.count; i++) + { + if(flavorModel.get(i).value == currentValue) { + index = i; + break; + } + } + return index + } + onActivated: + { + machineGCodeFlavorProvider.setPropertyValue("value", flavorModel.get(index).value); + manager.updateHasMaterialsMetadata(); + } } } } - CheckBox + + Column { - id: centerIsZeroCheckBox - text: catalog.i18nc("@option:check", "Machine Center is Zero") - checked: String(machineCenterIsZeroProvider.properties.value).toLowerCase() != 'false' - onClicked: + width: settingsTabs.columnWidth + spacing: UM.Theme.getSize("default_margin").height + + Label { - machineCenterIsZeroProvider.setPropertyValue("value", checked); - manager.forceUpdate(); + text: catalog.i18nc("@label", "Printhead Settings") + font.bold: true + } + + Grid + { + columns: 2 + columnSpacing: UM.Theme.getSize("default_margin").width + rowSpacing: UM.Theme.getSize("default_lining").width + + Label + { + text: catalog.i18nc("@label", "X min") + } + TextField + { + id: printheadXMinField + text: getHeadPolygonCoord("x", "min") + validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ } + onEditingFinished: setHeadPolygon() + } + + Label + { + text: catalog.i18nc("@label", "Y min") + } + TextField + { + id: printheadYMinField + text: getHeadPolygonCoord("y", "min") + validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ } + onEditingFinished: setHeadPolygon() + } + + Label + { + text: catalog.i18nc("@label", "X max") + } + TextField + { + id: printheadXMaxField + text: getHeadPolygonCoord("x", "max") + validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ } + onEditingFinished: setHeadPolygon() + } + + Label + { + text: catalog.i18nc("@label", "Y max") + } + TextField + { + id: printheadYMaxField + text: getHeadPolygonCoord("y", "max") + validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ } + onEditingFinished: setHeadPolygon() + } + + Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height } + Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height } + + Label + { + text: catalog.i18nc("@label", "Gantry height") + } + Loader + { + id: gantryHeightField + sourceComponent: numericTextFieldWithUnit + property var propertyProvider: gantryHeightProvider + property string unit: catalog.i18nc("@label", "mm") + property bool forceUpdateOnChange: false + } + + Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height } + Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height } + + Label + { + text: catalog.i18nc("@label", "Number of Extruders") + visible: extruderCountComboBox.visible + } + + ComboBox + { + id: extruderCountComboBox + visible: manager.definedExtruderCount > 1 + model: ListModel + { + id: extruderCountModel + Component.onCompleted: + { + for(var i = 0; i < manager.definedExtruderCount; i++) + { + extruderCountModel.append({text: String(i + 1), value: i}); + } + } + } + currentIndex: machineExtruderCountProvider.properties.value - 1 + onActivated: + { + manager.setMachineExtruderCount(index + 1); + } + } + + Label + { + text: catalog.i18nc("@label", "Material Diameter") + } + Loader + { + id: materialDiameterField + sourceComponent: numericTextFieldWithUnit + property var propertyProvider: materialDiameterProvider + property string unit: catalog.i18nc("@label", "mm") + property bool forceUpdateOnChange: false + } + Label + { + text: catalog.i18nc("@label", "Nozzle size") + visible: nozzleSizeField.visible + } + Loader + { + id: nozzleSizeField + visible: !Cura.MachineManager.hasVariants && machineExtruderCountProvider.properties.value == 1 + sourceComponent: numericTextFieldWithUnit + property var propertyProvider: machineNozzleSizeProvider + property string unit: catalog.i18nc("@label", "mm") + property bool forceUpdateOnChange: false + } } - } - CheckBox - { - id: heatedBedCheckBox - text: catalog.i18nc("@option:check", "Heated Bed") - checked: String(machineHeatedBedProvider.properties.value).toLowerCase() != 'false' - onClicked: machineHeatedBedProvider.setPropertyValue("value", checked) } } Row { spacing: UM.Theme.getSize("default_margin").width - - Label + anchors.left: parent.left + anchors.right: parent.right + height: parent.height - y + Column { - text: catalog.i18nc("@label", "GCode Flavor") - } - - ComboBox - { - model: ListModel + height: parent.height + width: settingsTabs.columnWidth + Label { - id: flavorModel + text: catalog.i18nc("@label", "Start Gcode") + font.bold: true + } + TextArea + { + id: machineStartGcodeField + width: parent.width + height: parent.height - y + font: UM.Theme.getFont("fixed") + text: machineStartGcodeProvider.properties.value + onActiveFocusChanged: + { + if(!activeFocus) + { + machineStartGcodeProvider.setPropertyValue("value", machineStartGcodeField.text) + } + } Component.onCompleted: { - // Options come in as a string-representation of an OrderedDict - var options = machineGCodeFlavorProvider.properties.options.match(/^OrderedDict\(\[\((.*)\)\]\)$/); - if(options) + wrapMode = TextEdit.NoWrap; + } + } + } + + Column { + height: parent.height + width: settingsTabs.columnWidth + Label + { + text: catalog.i18nc("@label", "End Gcode") + font.bold: true + } + TextArea + { + id: machineEndGcodeField + width: parent.width + height: parent.height - y + font: UM.Theme.getFont("fixed") + text: machineEndGcodeProvider.properties.value + onActiveFocusChanged: + { + if(!activeFocus) { - options = options[1].split("), (") - for(var i = 0; i < options.length; i++) + machineEndGcodeProvider.setPropertyValue("value", machineEndGcodeField.text) + } + } + Component.onCompleted: + { + wrapMode = TextEdit.NoWrap; + } + } + } + } + + function getHeadPolygonCoord(axis, minMax) + { + var polygon = JSON.parse(machineHeadPolygonProvider.properties.value); + var item = (axis == "x") ? 0 : 1 + var result = polygon[0][item]; + for(var i = 1; i < polygon.length; i++) { + if (minMax == "min") { + result = Math.min(result, polygon[i][item]); + } else { + result = Math.max(result, polygon[i][item]); + } + } + return Math.abs(result); + } + + function setHeadPolygon() + { + var polygon = []; + polygon.push([-parseFloat(printheadXMinField.text), parseFloat(printheadYMaxField.text)]); + polygon.push([-parseFloat(printheadXMinField.text),-parseFloat(printheadYMinField.text)]); + polygon.push([ parseFloat(printheadXMaxField.text), parseFloat(printheadYMaxField.text)]); + polygon.push([ parseFloat(printheadXMaxField.text),-parseFloat(printheadYMinField.text)]); + var polygon_string = JSON.stringify(polygon); + if(polygon != machineHeadPolygonProvider.properties.value) + { + machineHeadPolygonProvider.setPropertyValue("value", polygon_string); + manager.forceUpdate(); + } + } + } + } + + onCurrentIndexChanged: + { + if(currentIndex > 0) + { + contentItem.forceActiveFocus(); + ExtruderManager.setActiveExtruderIndex(currentIndex - 1); + } + } + + Repeater + { + id: extruderTabsRepeater + model: base.extruderTabsCount + + Tab + { + title: base.extrudersModel.getItem(index).name + anchors.margins: UM.Theme.getSize("default_margin").width + + Column + { + spacing: UM.Theme.getSize("default_margin").width + + Label + { + text: catalog.i18nc("@label", "Nozzle Settings") + font.bold: true + } + + Grid + { + columns: 2 + columnSpacing: UM.Theme.getSize("default_margin").width + rowSpacing: UM.Theme.getSize("default_lining").width + + Label + { + text: catalog.i18nc("@label", "Nozzle size") + visible: extruderNozzleSizeField.visible + } + Loader + { + id: extruderNozzleSizeField + visible: !Cura.MachineManager.hasVariants + sourceComponent: numericTextFieldWithUnit + property var propertyProvider: extruderNozzleSizeProvider + property string unit: catalog.i18nc("@label", "mm") + property bool forceUpdateOnChange: false + } + + Label + { + text: catalog.i18nc("@label", "Nozzle offset X") + } + Loader + { + id: extruderOffsetXField + sourceComponent: numericTextFieldWithUnit + property var propertyProvider: extruderOffsetXProvider + property string unit: catalog.i18nc("@label", "mm") + property bool forceUpdateOnChange: true + } + Label + { + text: catalog.i18nc("@label", "Nozzle offset Y") + } + Loader + { + id: extruderOffsetYField + sourceComponent: numericTextFieldWithUnit + property var propertyProvider: extruderOffsetYProvider + property string unit: catalog.i18nc("@label", "mm") + property bool forceUpdateOnChange: true + } + } + + Row + { + spacing: UM.Theme.getSize("default_margin").width + anchors.left: parent.left + anchors.right: parent.right + height: parent.height - y + Column + { + height: parent.height + width: settingsTabs.columnWidth + Label + { + text: catalog.i18nc("@label", "Extruder Start Gcode") + font.bold: true + } + TextArea + { + id: extruderStartGcodeField + width: parent.width + height: parent.height - y + font: UM.Theme.getFont("fixed") + text: (extruderStartGcodeProvider.properties.value) ? extruderStartGcodeProvider.properties.value : "" + onActiveFocusChanged: + { + if(!activeFocus) { - var option = options[i].substring(1, options[i].length - 1).split("', '") - flavorModel.append({text: option[1], value: option[0]}); + extruderStartGcodeProvider.setPropertyValue("value", extruderStartGcodeField.text) } } - } - } - currentIndex: - { - var currentValue = machineGCodeFlavorProvider.properties.value; - var index = 0; - for(var i = 0; i < flavorModel.count; i++) - { - if(flavorModel.get(i).value == currentValue) { - index = i; - break; + Component.onCompleted: + { + wrapMode = TextEdit.NoWrap; } } - return index } - onActivated: - { - machineGCodeFlavorProvider.setPropertyValue("value", flavorModel.get(index).value); - manager.updateHasMaterialsMetadata(); + Column { + height: parent.height + width: settingsTabs.columnWidth + Label + { + text: catalog.i18nc("@label", "Extruder End Gcode") + font.bold: true + } + TextArea + { + id: extruderEndGcodeField + width: parent.width + height: parent.height - y + font: UM.Theme.getFont("fixed") + text: (extruderEndGcodeProvider.properties.value) ? extruderEndGcodeProvider.properties.value : "" + onActiveFocusChanged: + { + if(!activeFocus) + { + extruderEndGcodeProvider.setPropertyValue("value", extruderEndGcodeField.text) + } + } + Component.onCompleted: + { + wrapMode = TextEdit.NoWrap; + } + } } } } } - - Column - { - width: parent.width / 2 - spacing: UM.Theme.getSize("default_margin").height - - Label - { - text: catalog.i18nc("@label", "Printhead Settings") - font.bold: true - } - - Grid - { - columns: 3 - columnSpacing: UM.Theme.getSize("default_margin").width - - Label - { - text: catalog.i18nc("@label", "X min") - } - TextField - { - id: printheadXMinField - text: getHeadPolygonCoord("x", "min") - validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ } - onEditingFinished: setHeadPolygon() - } - Label - { - text: catalog.i18nc("@label", "mm") - } - - Label - { - text: catalog.i18nc("@label", "Y min") - } - TextField - { - id: printheadYMinField - text: getHeadPolygonCoord("y", "min") - validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ } - onEditingFinished: setHeadPolygon() - } - Label - { - text: catalog.i18nc("@label", "mm") - } - - Label - { - text: catalog.i18nc("@label", "X max") - } - TextField - { - id: printheadXMaxField - text: getHeadPolygonCoord("x", "max") - validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ } - onEditingFinished: setHeadPolygon() - } - Label - { - text: catalog.i18nc("@label", "mm") - } - - Label - { - text: catalog.i18nc("@label", "Y max") - } - TextField - { - id: printheadYMaxField - text: getHeadPolygonCoord("y", "max") - validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ } - onEditingFinished: setHeadPolygon() - } - Label - { - text: catalog.i18nc("@label", "mm") - } - - Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height } - Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height } - Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height } - - Label - { - text: catalog.i18nc("@label", "Gantry height") - } - TextField - { - id: gantryHeightField - text: gantryHeightProvider.properties.value - validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ } - onEditingFinished: { gantryHeightProvider.setPropertyValue("value", text) } - } - Label - { - text: catalog.i18nc("@label", "mm") - } - - Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height } - Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height } - Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height } - - Label - { - text: catalog.i18nc("@label", "Nozzle size") - visible: !Cura.MachineManager.hasVariants - } - TextField - { - id: nozzleSizeField - text: machineNozzleSizeProvider.properties.value - visible: !Cura.MachineManager.hasVariants - validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ } - onEditingFinished: { machineNozzleSizeProvider.setPropertyValue("value", text) } - } - Label - { - text: catalog.i18nc("@label", "mm") - visible: !Cura.MachineManager.hasVariants - } - } - } } + } + } - Row + Component + { + id: numericTextFieldWithUnit + Item { + height: textField.height + width: textField.width + TextField { - spacing: UM.Theme.getSize("default_margin").width - anchors.left: parent.left - anchors.right: parent.right - height: parent.height - y - Column + id: textField + text: (propertyProvider.properties.value) ? propertyProvider.properties.value : "" + validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ } + onEditingFinished: { - height: parent.height - width: parent.width / 2 - Label + if (propertyProvider && text != propertyProvider.properties.value) { - text: catalog.i18nc("@label", "Start Gcode") - } - TextArea - { - id: machineStartGcodeField - width: parent.width - height: parent.height - y - font: UM.Theme.getFont("fixed") - wrapMode: TextEdit.NoWrap - text: machineStartGcodeProvider.properties.value - onActiveFocusChanged: + propertyProvider.setPropertyValue("value", text); + if(forceUpdateOnChange) { - if(!activeFocus) + var extruderIndex = ExtruderManager.activeExtruderIndex; + manager.forceUpdate(); + if(ExtruderManager.activeExtruderIndex != extruderIndex) { - machineStartGcodeProvider.setPropertyValue("value", machineStartGcodeField.text) - } - } - } - } - Column { - height: parent.height - width: parent.width / 2 - Label - { - text: catalog.i18nc("@label", "End Gcode") - } - TextArea - { - id: machineEndGcodeField - width: parent.width - height: parent.height - y - font: UM.Theme.getFont("fixed") - wrapMode: TextEdit.NoWrap - text: machineEndGcodeProvider.properties.value - onActiveFocusChanged: - { - if(!activeFocus) - { - machineEndGcodeProvider.setPropertyValue("value", machineEndGcodeField.text) + ExtruderManager.setActiveExtruderIndex(extruderIndex) } } } } } - } - } - function getHeadPolygonCoord(axis, minMax) - { - var polygon = JSON.parse(machineHeadPolygonProvider.properties.value); - var item = (axis == "x") ? 0 : 1 - var result = polygon[0][item]; - for(var i = 1; i < polygon.length; i++) { - if (minMax == "min") { - result = Math.min(result, polygon[i][item]); - } else { - result = Math.max(result, polygon[i][item]); + Label + { + text: unit + anchors.right: textField.right + anchors.rightMargin: y - textField.y + anchors.verticalCenter: textField.verticalCenter } } - return Math.abs(result); - } - - function setHeadPolygon() - { - var polygon = []; - polygon.push([-parseFloat(printheadXMinField.text), parseFloat(printheadYMaxField.text)]); - polygon.push([-parseFloat(printheadXMinField.text),-parseFloat(printheadYMinField.text)]); - polygon.push([ parseFloat(printheadXMaxField.text), parseFloat(printheadYMaxField.text)]); - polygon.push([ parseFloat(printheadXMaxField.text),-parseFloat(printheadYMinField.text)]); - machineHeadPolygonProvider.setPropertyValue("value", JSON.stringify(polygon)); - manager.forceUpdate(); } UM.SettingPropertyProvider @@ -522,6 +758,16 @@ Cura.MachineAction storeIndex: manager.containerIndex } + UM.SettingPropertyProvider + { + id: materialDiameterProvider + + containerStackId: Cura.MachineManager.activeMachineId + key: "material_diameter" + watchedProperties: [ "value" ] + storeIndex: manager.containerIndex + } + UM.SettingPropertyProvider { id: machineNozzleSizeProvider @@ -532,6 +778,16 @@ Cura.MachineAction storeIndex: manager.containerIndex } + UM.SettingPropertyProvider + { + id: machineExtruderCountProvider + + containerStackId: Cura.MachineManager.activeMachineId + key: "machine_extruder_count" + watchedProperties: [ "value" ] + storeIndex: manager.containerIndex + } + UM.SettingPropertyProvider { id: gantryHeightProvider @@ -573,4 +829,53 @@ Cura.MachineAction storeIndex: manager.containerIndex } + UM.SettingPropertyProvider + { + id: extruderNozzleSizeProvider + + containerStackId: settingsTabs.currentIndex > 0 ? Cura.MachineManager.activeStackId : "" + key: "machine_nozzle_size" + watchedProperties: [ "value" ] + storeIndex: manager.containerIndex + } + + UM.SettingPropertyProvider + { + id: extruderOffsetXProvider + + containerStackId: settingsTabs.currentIndex > 0 ? Cura.MachineManager.activeStackId : "" + key: "machine_nozzle_offset_x" + watchedProperties: [ "value" ] + storeIndex: manager.containerIndex + } + + UM.SettingPropertyProvider + { + id: extruderOffsetYProvider + + containerStackId: settingsTabs.currentIndex > 0 ? Cura.MachineManager.activeStackId : "" + key: "machine_nozzle_offset_y" + watchedProperties: [ "value" ] + storeIndex: manager.containerIndex + } + + UM.SettingPropertyProvider + { + id: extruderStartGcodeProvider + + containerStackId: settingsTabs.currentIndex > 0 ? Cura.MachineManager.activeStackId : "" + key: "machine_extruder_start_code" + watchedProperties: [ "value" ] + storeIndex: manager.containerIndex + } + + UM.SettingPropertyProvider + { + id: extruderEndGcodeProvider + + containerStackId: settingsTabs.currentIndex > 0 ? Cura.MachineManager.activeStackId : "" + key: "machine_extruder_end_code" + watchedProperties: [ "value" ] + storeIndex: manager.containerIndex + } } \ No newline at end of file diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml index cb65da635b..3e78670e02 100644 --- a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml +++ b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml @@ -26,129 +26,6 @@ Item { spacing: UM.Theme.getSize("default_margin").height - Row - { - spacing: UM.Theme.getSize("default_margin").width - Label - { - text: catalog.i18nc("@label Followed by extruder selection drop-down.", "Print model with") - anchors.verticalCenter: extruderSelector.verticalCenter - - color: UM.Theme.getColor("setting_control_text") - font: UM.Theme.getFont("default") - visible: extruderSelector.visible - } - ComboBox - { - id: extruderSelector - - model: Cura.ExtrudersModel - { - id: extrudersModel - onModelChanged: extruderSelector.color = extrudersModel.getItem(extruderSelector.currentIndex).color - } - property string color: extrudersModel.getItem(extruderSelector.currentIndex).color - visible: machineExtruderCount.properties.value > 1 - textRole: "name" - width: UM.Theme.getSize("setting_control").width - height: UM.Theme.getSize("section").height - MouseArea - { - anchors.fill: parent - acceptedButtons: Qt.NoButton - onWheel: wheel.accepted = true; - } - - style: ComboBoxStyle - { - background: Rectangle - { - color: - { - if(extruderSelector.hovered || base.activeFocus) - { - return UM.Theme.getColor("setting_control_highlight"); - } - else - { - return UM.Theme.getColor("setting_control"); - } - } - border.width: UM.Theme.getSize("default_lining").width - border.color: UM.Theme.getColor("setting_control_border") - } - label: Item - { - Rectangle - { - id: swatch - height: UM.Theme.getSize("setting_control").height / 2 - width: height - anchors.left: parent.left - anchors.leftMargin: UM.Theme.getSize("default_lining").width - anchors.verticalCenter: parent.verticalCenter - - color: extruderSelector.color - border.width: UM.Theme.getSize("default_lining").width - border.color: !enabled ? UM.Theme.getColor("setting_control_disabled_border") : UM.Theme.getColor("setting_control_border") - } - Label - { - anchors.left: swatch.right - anchors.leftMargin: UM.Theme.getSize("default_lining").width - anchors.right: downArrow.left - anchors.rightMargin: UM.Theme.getSize("default_lining").width - anchors.verticalCenter: parent.verticalCenter - - text: extruderSelector.currentText - font: UM.Theme.getFont("default") - color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text") - - elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter - } - - UM.RecolorImage - { - id: downArrow - anchors.right: parent.right - anchors.rightMargin: UM.Theme.getSize("default_lining").width * 2 - anchors.verticalCenter: parent.verticalCenter - - source: UM.Theme.getIcon("arrow_bottom") - width: UM.Theme.getSize("standard_arrow").width - height: UM.Theme.getSize("standard_arrow").height - sourceSize.width: width + 5 - sourceSize.height: width + 5 - - color: UM.Theme.getColor("setting_control_text") - } - } - } - - onActivated: - { - UM.ActiveTool.setProperty("SelectedActiveExtruder", extrudersModel.getItem(index).id); - extruderSelector.color = extrudersModel.getItem(index).color; - } - onModelChanged: updateCurrentIndex(); - - function updateCurrentIndex() - { - for(var i = 0; i < extrudersModel.rowCount(); ++i) - { - if(extrudersModel.getItem(i).id == UM.ActiveTool.properties.getValue("SelectedActiveExtruder")) - { - extruderSelector.currentIndex = i; - extruderSelector.color = extrudersModel.getItem(i).color; - return; - } - } - extruderSelector.currentIndex = -1; - } - } - } - Column { // This is to ensure that the panel is first increasing in size up to 200 and then shows a scrollbar. @@ -264,14 +141,6 @@ Item { storeIndex: 0 removeUnusedValue: false } - - // If the extruder by which the object needs to be printed is changed, ensure that the - // display is also notified of the fact. - Connections - { - target: extruderSelector - onActivated: provider.forcePropertiesChanged() - } } } } diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py b/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py index 865401804c..b2d14942ba 100644 --- a/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py +++ b/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py @@ -112,4 +112,4 @@ class PerObjectSettingsTool(Tool): self._single_model_selected = False # Group is selected, so tool needs to be disabled else: self._single_model_selected = True - Application.getInstance().getController().toolEnabledChanged.emit(self._plugin_id, (self._advanced_mode or self._multi_extrusion) and self._single_model_selected) \ No newline at end of file + Application.getInstance().getController().toolEnabledChanged.emit(self._plugin_id, self._advanced_mode and self._single_model_selected) diff --git a/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml b/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml index a80ed1d179..27271f0d15 100644 --- a/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml +++ b/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml @@ -342,6 +342,8 @@ Cura.MachineAction { regExp: /[a-zA-Z0-9\.\-\_]*/ } + + onAccepted: btnOk.clicked() } } @@ -355,6 +357,7 @@ Cura.MachineAction } }, Button { + id: btnOk text: catalog.i18nc("@action:button", "Ok") onClicked: { diff --git a/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py b/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py index 459de3248d..9b0eee7096 100755 --- a/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py +++ b/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py @@ -638,7 +638,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice): if self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"] == "": Logger.log("e", "No cartridge loaded in slot %s, unable to start print", index + 1) self._error_message = Message( - i18n_catalog.i18nc("@info:status", "Unable to start a new print job. No PrinterCore loaded in slot {0}".format(index + 1))) + i18n_catalog.i18nc("@info:status", "Unable to start a new print job. No Printcore loaded in slot {0}".format(index + 1))) self._error_message.show() return if self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["guid"] == "": diff --git a/plugins/USBPrinting/USBPrinterOutputDeviceManager.py b/plugins/USBPrinting/USBPrinterOutputDeviceManager.py index 5b1d0b8dac..7a62a59a45 100644 --- a/plugins/USBPrinting/USBPrinterOutputDeviceManager.py +++ b/plugins/USBPrinting/USBPrinterOutputDeviceManager.py @@ -259,7 +259,7 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension): i = 0 while True: values = winreg.EnumValue(key, i) - if not only_list_usb or "USBSER" in values[0]: + if not only_list_usb or "USBSER" or "VCP" in values[0]: base_list += [values[1]] i += 1 except Exception as e: diff --git a/plugins/UltimakerMachineActions/UM2UpgradeSelection.py b/plugins/UltimakerMachineActions/UM2UpgradeSelection.py new file mode 100644 index 0000000000..6c972efbf0 --- /dev/null +++ b/plugins/UltimakerMachineActions/UM2UpgradeSelection.py @@ -0,0 +1,65 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Uranium is released under the terms of the AGPLv3 or higher. + +from UM.Settings.ContainerRegistry import ContainerRegistry +from UM.Settings.InstanceContainer import InstanceContainer +from cura.MachineAction import MachineAction +from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty + +from UM.i18n import i18nCatalog +from UM.Application import Application +from UM.Util import parseBool +catalog = i18nCatalog("cura") + +import UM.Settings.InstanceContainer + + +## The Ultimaker 2 can have a few revisions & upgrades. +class UM2UpgradeSelection(MachineAction): + def __init__(self): + super().__init__("UM2UpgradeSelection", catalog.i18nc("@action", "Select upgrades")) + self._qml_url = "UM2UpgradeSelectionMachineAction.qml" + + self._container_registry = ContainerRegistry.getInstance() + + def _reset(self): + self.hasVariantsChanged.emit() + + hasVariantsChanged = pyqtSignal() + + @pyqtProperty(bool, notify = hasVariantsChanged) + def hasVariants(self): + global_container_stack = Application.getInstance().getGlobalContainerStack() + if global_container_stack: + return parseBool(global_container_stack.getMetaDataEntry("has_variants", "false")) + + @pyqtSlot(bool) + def setHasVariants(self, has_variants = True): + global_container_stack = Application.getInstance().getGlobalContainerStack() + if global_container_stack: + variant_container = global_container_stack.variant + variant_index = global_container_stack.getContainerIndex(variant_container) + + if has_variants: + if "has_variants" in global_container_stack.getMetaData(): + global_container_stack.setMetaDataEntry("has_variants", True) + else: + global_container_stack.addMetaDataEntry("has_variants", True) + + # Set the variant container to a sane default + empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() + if type(variant_container) == type(empty_container): + search_criteria = { "type": "variant", "definition": "ultimaker2", "id": "*0.4*" } + containers = self._container_registry.findInstanceContainers(**search_criteria) + if containers: + global_container_stack.variant = containers[0] + else: + # The metadata entry is stored in an ini, and ini files are parsed as strings only. + # Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False. + if "has_variants" in global_container_stack.getMetaData(): + global_container_stack.removeMetaDataEntry("has_variants") + + # Set the variant container to an empty variant + global_container_stack.variant = ContainerRegistry.getInstance().getEmptyInstanceContainer() + + Application.getInstance().globalContainerStackChanged.emit() diff --git a/plugins/UltimakerMachineActions/UM2UpgradeSelectionMachineAction.qml b/plugins/UltimakerMachineActions/UM2UpgradeSelectionMachineAction.qml new file mode 100644 index 0000000000..5e0096c6b0 --- /dev/null +++ b/plugins/UltimakerMachineActions/UM2UpgradeSelectionMachineAction.qml @@ -0,0 +1,52 @@ +// Copyright (c) 2016 Ultimaker B.V. +// Cura is released under the terms of the AGPLv3 or higher. + +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Layouts 1.1 +import QtQuick.Window 2.1 + +import UM 1.2 as UM +import Cura 1.0 as Cura + + +Cura.MachineAction +{ + anchors.fill: parent; + Item + { + id: upgradeSelectionMachineAction + anchors.fill: parent + + Label + { + id: pageTitle + width: parent.width + text: catalog.i18nc("@title", "Select Printer Upgrades") + wrapMode: Text.WordWrap + font.pointSize: 18; + } + + Label + { + id: pageDescription + anchors.top: pageTitle.bottom + anchors.topMargin: UM.Theme.getSize("default_margin").height + width: parent.width + wrapMode: Text.WordWrap + text: catalog.i18nc("@label","Please select any upgrades made to this Ultimaker 2."); + } + + CheckBox + { + anchors.top: pageDescription.bottom + anchors.topMargin: UM.Theme.getSize("default_margin").height + + text: catalog.i18nc("@label", "Olsson Block") + checked: manager.hasVariants + onClicked: manager.setHasVariants(checked) + } + + UM.I18nCatalog { id: catalog; name: "cura"; } + } +} \ No newline at end of file diff --git a/plugins/UltimakerMachineActions/__init__.py b/plugins/UltimakerMachineActions/__init__.py index fb0b2b1f64..996026ace2 100644 --- a/plugins/UltimakerMachineActions/__init__.py +++ b/plugins/UltimakerMachineActions/__init__.py @@ -5,6 +5,7 @@ from . import BedLevelMachineAction from . import UpgradeFirmwareMachineAction from . import UMOCheckupMachineAction from . import UMOUpgradeSelection +from . import UM2UpgradeSelection from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") @@ -21,4 +22,10 @@ def getMetaData(): } def register(app): - return { "machine_action": [BedLevelMachineAction.BedLevelMachineAction(), UpgradeFirmwareMachineAction.UpgradeFirmwareMachineAction(), UMOCheckupMachineAction.UMOCheckupMachineAction(), UMOUpgradeSelection.UMOUpgradeSelection()]} + return { "machine_action": [ + BedLevelMachineAction.BedLevelMachineAction(), + UpgradeFirmwareMachineAction.UpgradeFirmwareMachineAction(), + UMOCheckupMachineAction.UMOCheckupMachineAction(), + UMOUpgradeSelection.UMOUpgradeSelection(), + UM2UpgradeSelection.UM2UpgradeSelection() + ]} diff --git a/plugins/VersionUpgrade/VersionUpgrade24to25/VersionUpgrade24to25.py b/plugins/VersionUpgrade/VersionUpgrade24to25/VersionUpgrade24to25.py index 99a0f95a77..1af2e7405a 100644 --- a/plugins/VersionUpgrade/VersionUpgrade24to25/VersionUpgrade24to25.py +++ b/plugins/VersionUpgrade/VersionUpgrade24to25/VersionUpgrade24to25.py @@ -10,6 +10,10 @@ _removed_settings = { #Settings that were removed in 2.5. "start_layers_at_same_position" } +_split_settings = { #These settings should be copied to all settings it was split into. + "support_interface_line_distance": {"support_roof_line_distance", "support_bottom_line_distance"} +} + ## A collection of functions that convert the configuration of the user in Cura # 2.4 to a configuration for Cura 2.5. # @@ -42,8 +46,16 @@ class VersionUpgrade24to25(VersionUpgrade): #Remove settings from the visible_settings. if parser.has_section("general") and "visible_settings" in parser["general"]: visible_settings = parser["general"]["visible_settings"].split(";") - visible_settings = filter(lambda setting: setting not in _removed_settings, visible_settings) - parser["general"]["visible_settings"] = ";".join(visible_settings) + new_visible_settings = [] + for setting in visible_settings: + if setting in _removed_settings: + continue #Skip. + if setting in _split_settings: + for replaced_setting in _split_settings[setting]: + new_visible_settings.append(replaced_setting) + continue #Don't add the original. + new_visible_settings.append(setting) #No special handling, so just add the original visible setting back. + parser["general"]["visible_settings"] = ";".join(new_visible_settings) #Change the version number in the file. if parser.has_section("general"): #It better have! @@ -66,6 +78,10 @@ class VersionUpgrade24to25(VersionUpgrade): if parser.has_section("values"): for removed_setting in (_removed_settings & parser["values"].keys()): #Both in keys that need to be removed and in keys present in the file. del parser["values"][removed_setting] + for replaced_setting in (_split_settings.keys() & parser["values"].keys()): + for replacement in _split_settings[replaced_setting]: + parser["values"][replacement] = parser["values"][replaced_setting] #Copy to replacement before removing the original! + del replaced_setting #Change the version number in the file. if parser.has_section("general"): diff --git a/resources/definitions/alya3dp.def.json b/resources/definitions/alya3dp.def.json new file mode 100644 index 0000000000..1ea16773d6 --- /dev/null +++ b/resources/definitions/alya3dp.def.json @@ -0,0 +1,55 @@ +{ + "id": "alya3dp", + "name": "ALYA", + "version": 2, + "inherits": "fdmprinter", + "metadata": { + "visible": true, + "author": "ALYA", + "manufacturer": "ALYA", + "category": "Other", + "file_formats": "text/x-gcode" + }, + + "overrides": { + "machine_width": { + "default_value": 100 + }, + "machine_height": { + "default_value": 133 + }, + "machine_depth": { + "default_value": 100 + }, + "machine_center_is_zero": { + "default_value": false + }, + "machine_nozzle_size": { + "default_value": 0.4 + }, + "machine_head_shape_min_x": { + "default_value": 75 + }, + "machine_head_shape_min_y": { + "default_value": 18 + }, + "machine_head_shape_max_x": { + "default_value": 18 + }, + "machine_head_shape_max_y": { + "default_value": 35 + }, + "machine_nozzle_gantry_distance": { + "default_value": 55 + }, + "machine_gcode_flavor": { + "default_value": "RepRap" + }, + "machine_start_gcode": { + "default_value": ";Sliced at: {day} {date} {time}\n;Basic settings: Layer height: {layer_height} Walls: {wall_thickness} Fill: {fill_density}\n;Print time: {print_time}\n;Filament used: {filament_amount}m {filament_weight}g\n;Filament cost: {filament_cost}\n;M190 S{print_bed_temperature} ;Uncomment to add your own bed temperature line\n;M109 S{print_temperature} ;Uncomment to add your own temperature line\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to max endstops\nG1 Z115.0 F{travel_speed} ;move th e platform up 20mm\nG28 Z0 ;move Z to max endstop\nG1 Z15.0 F{travel_speed} ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F{travel_speed}\nM301 H1 P26.38 I2.57 D67.78\n;Put printing message on LCD screen\nM117 Printing..." + }, + "machine_end_gcode": { + "default_value": ";End GCode\nM104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nG28 Z0\nM84 ;steppers off\nG90 ;absolute positioning\n;{profile_string}" + } + } +} \ No newline at end of file diff --git a/resources/definitions/cartesio.def.json b/resources/definitions/cartesio.def.json index bcaaa30380..6567f61fb3 100644 --- a/resources/definitions/cartesio.def.json +++ b/resources/definitions/cartesio.def.json @@ -17,9 +17,9 @@ "has_variants": true, "variants_name": "Nozzle size", - "preferred_variant": "*0.4*", + "preferred_variant": "*0.8*", "preferred_material": "*pla*", - "preferred_quality": "*normal*", + "preferred_quality": "*high*", "machine_extruder_trains": { @@ -35,7 +35,8 @@ }, "overrides": { - "machine_extruder_count": { "default_value": 4 }, + "machine_extruder_count": { "default_value": 2 }, + "material_diameter": { "default_value": 1.75 }, "machine_heated_bed": { "default_value": true }, "machine_center_is_zero": { "default_value": false }, "gantry_height": { "default_value": 35 }, @@ -45,20 +46,18 @@ "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" }, "material_print_temp_wait": { "default_value": false }, "material_bed_temp_wait": { "default_value": false }, - "infill_pattern": { "default_value": "grid"}, "prime_tower_enable": { "default_value": true }, "prime_tower_wall_thickness": { "resolve": 0.7 }, "prime_tower_position_x": { "default_value": 50 }, - "prime_tower_position_y": { "default_value": 71 }, + "prime_tower_position_y": { "default_value": 150 }, "machine_start_gcode": { - "default_value": "\nM104 S120 T1\nM104 S120 T2\nM104 S120 T3\n\nM92 E159\n\nG21\nG90\nM42 S255 P13;chamber lights\nM42 S255 P12;fume extraction\n\nM117 Homing Y ......\nG28 Y\nM117 Homing X ......\nG28 X\nM117 Homing Z ......\nG28 Z F100\nG1 Z10 F600\nG1 X70 Y20 F9000;go to wipe point\n\nM190 S{material_bed_temperature_layer_0}\n\nM117 Heating for 50 sec.\nG4 S20\nM117 Heating for 30 sec.\nG4 S20\nM117 Heating for 10 sec.\nM300 S600 P1000\nG4 S9\n\nM117 purging nozzle....\nT0\nG92 E0;set E\nG1 E10 F100\nG92 E0\nG1 E-1 F600\n\nM117 wiping nozzle....\nG1 X1 Y24 F3000\nG1 X70 F9000\n\nM104 S21 T1\nM104 S21 T2\nM104 S21 T3\n\nM117 Printing .....\n" + "default_value": "\nM104 S120 T1\nM104 S120 T2\nM104 S120 T3\n\nM92 E159\n\nG21\nG90\nM42 S255 P13;chamber lights\nM42 S255 P12;fume extraction\n\nM117 Homing Y ......\nG28 Y\nM117 Homing X ......\nG28 X\nM117 Homing Z ......\nG28 Z F100\nG1 Z10 F600\nG1 X70 Y20 F9000;go to wipe point\n\nM190 S{material_bed_temperature_layer_0}\n\nM117 Heating for 50 sec.\nG4 S20\nM117 Heating for 30 sec.\nG4 S20\nM117 Heating for 10 sec.\nM300 S1200 P1000\nG4 S9\n\nM117 purging nozzle....\nT0\nG92 E0;set E\nG1 E10 F100\nG92 E0\nG1 E-1 F600\n\nM117 wiping nozzle....\nG1 X1 Y24 F3000\nG1 X70 F9000\nG1 Z10 F900\n\nM104 S21 T1\nM104 S21 T2\nM104 S21 T3\n\nM117 Printing .....\n" }, "machine_end_gcode": { - "default_value": "; -- END GCODE --\nM106 S255\nM140 S5\nM104 S5 T0\nM104 S5 T1\nM104 S5 T2\nM104 S5 T3\nG1 X20.0 Y260.0 F6000\nG4 S7\nM84\nG4 S90\nM107\nM42 P12 S0\nM42 P13 S0\nM84\nT0\n; -- end of GCODE --" + "default_value": "; -- END GCODE --\nM106 S255\nM140 S5\nM104 S5 T0\nM104 S5 T1\nM104 S5 T2\nM104 S5 T3\n\nG91\nG1 Z1 F900\nG90\n\nG1 X20.0 Y260.0 F6000\nG4 S7\nM84\nG4 S90\nM107\nM42 P12 S0\nM42 P13 S0\nM84\nT0\n; -- end of GCODE --" }, "layer_height": { "maximum_value": "(0.8 * min(extruderValues('machine_nozzle_size')))" }, "layer_height_0": { "maximum_value": "(0.8 * min(extruderValues('machine_nozzle_size')))" }, - "layer_height_0": { "resolve": "0.2 if min(extruderValues('machine_nozzle_size')) < 0.3 else 0.3 "}, "machine_nozzle_heat_up_speed": {"default_value": 20}, "machine_nozzle_cool_down_speed": {"default_value": 20}, "machine_min_cool_heat_time_window": {"default_value": 5} diff --git a/resources/definitions/custom.def.json b/resources/definitions/custom.def.json index 7ae1d1bd28..8f15f00a0f 100644 --- a/resources/definitions/custom.def.json +++ b/resources/definitions/custom.def.json @@ -10,6 +10,17 @@ "category": "Custom", "file_formats": "text/x-gcode", "has_materials": true, + "machine_extruder_trains": + { + "0": "custom_extruder_1", + "1": "custom_extruder_2", + "2": "custom_extruder_3", + "3": "custom_extruder_4", + "4": "custom_extruder_5", + "5": "custom_extruder_6", + "6": "custom_extruder_7", + "7": "custom_extruder_8" + }, "first_start_actions": ["MachineSettingsAction"] } } diff --git a/resources/definitions/delta_go.def.json b/resources/definitions/delta_go.def.json index b341a17656..ccb659f973 100644 --- a/resources/definitions/delta_go.def.json +++ b/resources/definitions/delta_go.def.json @@ -14,23 +14,29 @@ }, "overrides": { "machine_name": { "default_value": "Delta Go" }, - "material_diameter": { "default_value": 1.75 }, + "material_diameter": { "default_value": 1.75 }, + "default_material_print_temperature": { "default_value": 210 }, "speed_travel": { "default_value": 150 }, - "prime_tower_size": { "default_value": 8.66 }, - "infill_sparse_density": { "default_value": 10 }, + "prime_tower_size": { "default_value": 8.66 }, + "infill_sparse_density": { "default_value": 10 }, "speed_wall_x": { "default_value": 30 }, "speed_wall_0": { "default_value": 30 }, "speed_topbottom": { "default_value": 20 }, - "layer_height": { "default_value": 0.2 }, + "layer_height": { "default_value": 0.15 }, "speed_print": { "default_value": 30 }, - "machine_heated_bed": { "default_value": false }, - "machine_center_is_zero": { "default_value": true }, - "machine_height": { "default_value": 127 }, - "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" }, + "machine_heated_bed": { "default_value": false }, + "machine_center_is_zero": { "default_value": true }, + "machine_height": { "default_value": 154 }, + "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" }, "machine_depth": { "default_value": 115 }, "machine_width": { "default_value": 115 }, - "retraction_amount": { "default_value": 4.2 }, - "retraction_speed": { "default_value": 400 }, - "machine_shape": { "default_value": "elliptic"} + "raft_airgap": { "default_value": 0.15 }, + "retraction_hop_enabled": { "value": "True" }, + "retraction_amount": { "default_value": 4.1 }, + "retraction_speed": { "default_value": 500 }, + "retraction_hop": { "value": "0.2" }, + "retraction_hop_only_when_collides": { "value": "True" }, + "brim_width": { "value": "5" }, + "machine_shape": { "default_value": "elliptic"} } } diff --git a/resources/definitions/fdmextruder.def.json b/resources/definitions/fdmextruder.def.json index 7c594e3eda..ff54582885 100644 --- a/resources/definitions/fdmextruder.def.json +++ b/resources/definitions/fdmextruder.def.json @@ -25,10 +25,22 @@ "type": "extruder", "default_value": "0", "settable_per_mesh": true, - "settable_per_extruder": false, + "settable_per_extruder": true, "settable_per_meshgroup": false, "settable_globally": false }, + "machine_nozzle_size": + { + "label": "Nozzle Diameter", + "description": "The inner diameter of the nozzle. Change this setting when using a non-standard nozzle size.", + "unit": "mm", + "type": "float", + "default_value": 0.4, + "minimum_value": "0.001", + "maximum_value_warning": "10", + "settable_per_mesh": false, + "settable_per_extruder": true + }, "machine_nozzle_offset_x": { "label": "Nozzle X Offset", diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 39d24e8788..46b3b125a4 100755 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -1346,7 +1346,7 @@ "description": "The height of infill of a given density before switching to half the density.", "unit": "mm", "type": "float", - "default_value": 5.0, + "default_value": 1.5, "minimum_value": "0.0001", "minimum_value_warning": "3 * resolveOrValue('layer_height')", "maximum_value_warning": "100", @@ -1383,8 +1383,8 @@ { "expand_upper_skins": { - "label": "Expand Upper Skins", - "description": "Expand upper skin areas (areas with air above) so that they support infill above.", + "label": "Expand Top Skins Into Infill", + "description": "Expand the top skin areas (areas with air above) so that they support infill above.", "type": "bool", "default_value": false, "value": "expand_skins_into_infill", @@ -1392,8 +1392,8 @@ }, "expand_lower_skins": { - "label": "Expand Lower Skins", - "description": "Expand lower skin areas (areas with air below) so that they are anchored by the infill layers above and below.", + "label": "Expand Bottom Skins Into Infill", + "description": "Expand the bottom skin areas (areas with air below) so that they are anchored by the infill layers above and below.", "type": "bool", "default_value": false, "settable_per_mesh": true @@ -3235,7 +3235,7 @@ "support_bottom_stair_step_height": { "label": "Support Stair Step Height", - "description": "The height of the steps of the stair-like bottom of support resting on the model. A low value makes the support harder to remove, but too high values can lead to unstable support structures.", + "description": "The height of the steps of the stair-like bottom of support resting on the model. A low value makes the support harder to remove, but too high values can lead to unstable support structures. Set to zero to turn off the stair-like behaviour.", "unit": "mm", "type": "float", "default_value": 0.3, @@ -3245,6 +3245,19 @@ "enabled": "support_enable", "settable_per_mesh": true }, + "support_bottom_stair_step_width": + { + "label": "Support Stair Step Maximum Width", + "description": "The maximum width of the steps of the stair-like bottom of support resting on the model. A low value makes the support harder to remove, but too high values can lead to unstable support structures.", + "unit": "mm", + "type": "float", + "default_value": 5.0, + "limit_to_extruder": "support_interface_extruder_nr if support_interface_enable else support_infill_extruder_nr", + "minimum_value": "0", + "maximum_value_warning": "10.0", + "enabled": "support_enable", + "settable_per_mesh": true + }, "support_join_distance": { "label": "Support Join Distance", @@ -3573,6 +3586,17 @@ "description": "Adhesion", "children": { + "prime_blob_enable": + { + "label": "Enable Prime Blob", + "description": "Whether to prime the filament with a blob before printing. Turning this setting on will ensure that the extruder will have material ready at the nozzle before printing. Printing Brim or Skirt can act like priming too, in which case turning this setting off saves some time.", + "type": "bool", + "resolve": "any(extruderValues('prime_blob_enable'))", + "default_value": true, + "settable_per_mesh": false, + "settable_per_extruder": true, + "enabled": false + }, "extruder_prime_pos_x": { "label": "Extruder Prime X Position", @@ -4192,7 +4216,7 @@ "type": "float", "unit": "mm", "enabled": "resolveOrValue('prime_tower_enable')", - "default_value": 15, + "default_value": 20, "resolve": "max(extruderValues('prime_tower_size'))", "minimum_value": "0", "maximum_value": "min(0.5 * machine_width, 0.5 * machine_depth)", @@ -4449,6 +4473,31 @@ "settable_per_meshgroup": false, "settable_globally": false }, + "infill_mesh_order": + { + "label": "Infill Mesh Order", + "description": "Determines which infill mesh is inside the infill of another infill mesh. An infill mesh with a higher order will modify the infill of infill meshes with lower order and normal meshes.", + "default_value": 0, + "value": "1 if infill_mesh else 0", + "minimum_value_warning": "1", + "maximum_value_warning": "50", + "type": "int", + "settable_per_mesh": true, + "settable_per_extruder": false, + "settable_per_meshgroup": false, + "settable_globally": false + }, + "cutting_mesh": + { + "label": "Cutting Mesh", + "description": "Limit the volume of this mesh to within other meshes. You can use this to make certain areas of one mesh print with different settings and with a whole different extruder.", + "type": "bool", + "default_value": false, + "settable_per_mesh": true, + "settable_per_extruder": false, + "settable_per_meshgroup": false, + "settable_globally": false + }, "mold_enabled": { "label": "Mold", @@ -4483,20 +4532,6 @@ "settable_per_mesh": true, "enabled": "mold_enabled" }, - "infill_mesh_order": - { - "label": "Infill Mesh Order", - "description": "Determines which infill mesh is inside the infill of another infill mesh. An infill mesh with a higher order will modify the infill of infill meshes with lower order and normal meshes.", - "default_value": 0, - "value": "1 if infill_mesh else 0", - "minimum_value_warning": "1", - "maximum_value_warning": "50", - "type": "int", - "settable_per_mesh": true, - "settable_per_extruder": false, - "settable_per_meshgroup": false, - "settable_globally": false - }, "support_mesh": { "label": "Support Mesh", @@ -4553,6 +4588,14 @@ "default_value": false, "settable_per_mesh": false, "settable_per_extruder": false + }, + "smooth_spiralized_contours": + { + "label": "Smooth Spiralized Contours", + "description": "Smooth the spiralized contours to reduce the visibility of the Z seam (the Z-seam should be barely visible on the print but will still be visible in the layer view). Note that smoothing will tend to blur fine surface details.", + "type": "bool", + "default_value": true, + "enabled": "magic_spiralize" } } }, diff --git a/resources/definitions/innovo_inventor.def.json b/resources/definitions/innovo_inventor.def.json index 40a2849979..4b169c5e31 100644 --- a/resources/definitions/innovo_inventor.def.json +++ b/resources/definitions/innovo_inventor.def.json @@ -44,12 +44,6 @@ "gantry_height": { "default_value": 82.3 }, - "machine_nozzle_offset_x": { - "default_value": 0 - }, - "machine_nozzle_offset_y": { - "default_value": 15 - }, "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" }, diff --git a/resources/definitions/maker_starter.def.json b/resources/definitions/maker_starter.def.json index 74cdc694ee..305a35d0ca 100644 --- a/resources/definitions/maker_starter.def.json +++ b/resources/definitions/maker_starter.def.json @@ -4,6 +4,7 @@ "name": "3DMaker Starter", "inherits": "fdmprinter", "metadata": { + "visible": true, "author": "tvlgiao", "manufacturer": "3DMaker", "category": "Other", diff --git a/resources/definitions/rigid3d_zero2.def.json b/resources/definitions/rigid3d_zero2.def.json index 73b50f0950..41cd2c18db 100644 --- a/resources/definitions/rigid3d_zero2.def.json +++ b/resources/definitions/rigid3d_zero2.def.json @@ -54,7 +54,7 @@ "speed_layer_0": { "value": 15 }, - "speed_tarvel": { + "speed_travel": { "value": 100 }, "support_enable": { diff --git a/resources/definitions/ultimaker2.def.json b/resources/definitions/ultimaker2.def.json index a52075fe5e..5b2f339589 100644 --- a/resources/definitions/ultimaker2.def.json +++ b/resources/definitions/ultimaker2.def.json @@ -15,7 +15,8 @@ "platform_texture": "Ultimaker2backplate.png", "platform_offset": [9, 0, 0], "has_materials": false, - "supported_actions":["UpgradeFirmware"] + "first_start_actions": ["UM2UpgradeSelection"], + "supported_actions":["UM2UpgradeSelection", "UpgradeFirmware"] }, "overrides": { "machine_name": { "default_value": "Ultimaker 2" }, diff --git a/resources/definitions/ultimaker2_go.def.json b/resources/definitions/ultimaker2_go.def.json index 5d4c898ade..5873dfbc90 100644 --- a/resources/definitions/ultimaker2_go.def.json +++ b/resources/definitions/ultimaker2_go.def.json @@ -13,6 +13,7 @@ "platform": "ultimaker2go_platform.obj", "platform_texture": "Ultimaker2Gobackplate.png", "platform_offset": [0, 0, 0], + "first_start_actions": [], "supported_actions":["UpgradeFirmware"] }, diff --git a/resources/definitions/ultimaker2_plus.def.json b/resources/definitions/ultimaker2_plus.def.json index 5b1c7909ba..d8169b9abb 100644 --- a/resources/definitions/ultimaker2_plus.def.json +++ b/resources/definitions/ultimaker2_plus.def.json @@ -16,6 +16,7 @@ "has_materials": true, "has_machine_materials": true, "has_machine_quality": true, + "first_start_actions": [], "supported_actions":["UpgradeFirmware"] }, diff --git a/resources/definitions/ultimaker3.def.json b/resources/definitions/ultimaker3.def.json index ad8b08dfa1..d1f8b19e36 100644 --- a/resources/definitions/ultimaker3.def.json +++ b/resources/definitions/ultimaker3.def.json @@ -73,6 +73,8 @@ "prime_tower_position_y": { "default_value": 178 }, "prime_tower_wipe_enabled": { "default_value": false }, + "prime_blob_enable": { "enabled": true }, + "acceleration_enabled": { "value": "True" }, "acceleration_layer_0": { "value": "acceleration_topbottom" }, "acceleration_prime_tower": { "value": "math.ceil(acceleration_print * 2000 / 4000)" }, @@ -103,7 +105,7 @@ "layer_height_0": { "value": "round(machine_nozzle_size / 1.5, 2)" }, "layer_start_x": { "value": "sum(extruderValues('machine_extruder_start_pos_x')) / len(extruderValues('machine_extruder_start_pos_x'))" }, "layer_start_y": { "value": "sum(extruderValues('machine_extruder_start_pos_y')) / len(extruderValues('machine_extruder_start_pos_y'))" }, - "line_width": { "value": "machine_nozzle_size * 0.875" }, + "line_width": { "value": "round(machine_nozzle_size * 0.875, 3)" }, "machine_min_cool_heat_time_window": { "value": "15" }, "default_material_print_temperature": { "value": "200" }, "material_print_temperature_layer_0": { "value": "material_print_temperature + 5" }, @@ -111,7 +113,7 @@ "material_bed_temperature_layer_0": { "maximum_value": "115" }, "material_standby_temperature": { "value": "100" }, "multiple_mesh_overlap": { "value": "0" }, - "prime_tower_enable": { "value": "True" }, + "prime_tower_enable": { "default_value": true }, "raft_airgap": { "value": "0" }, "raft_base_thickness": { "value": "0.3" }, "raft_interface_line_spacing": { "value": "0.5" }, diff --git a/resources/extruders/cartesio_extruder_0.def.json b/resources/extruders/cartesio_extruder_0.def.json index 65db56403c..9b25b366f7 100644 --- a/resources/extruders/cartesio_extruder_0.def.json +++ b/resources/extruders/cartesio_extruder_0.def.json @@ -19,7 +19,7 @@ "default_value": "\n;start extruder_0\n\nM117 printing\n" }, "machine_extruder_end_code": { - "default_value": "\nM104 T0 S160\nG91\nG1 Z0.5 F900\nG90\nG1 X10 Y260 F9000\n;end extruder_0\nM117 temp is {material_print_temp}" + "default_value": "\nM104 T0 S160\nG91\nG1 Z5 F900\nG90\nG1 X1 Y260 F9000\n;end extruder_0\nM117 temp is {material_print_temp}" } } } diff --git a/resources/extruders/cartesio_extruder_1.def.json b/resources/extruders/cartesio_extruder_1.def.json index a6f353cf73..939b1fd7c8 100644 --- a/resources/extruders/cartesio_extruder_1.def.json +++ b/resources/extruders/cartesio_extruder_1.def.json @@ -19,7 +19,7 @@ "default_value": "\n;start extruder_1\n\nM117 printing\n" }, "machine_extruder_end_code": { - "default_value": "\nM104 T1 S160\nG91\nG1 Z0.5 F900\nG90\nG1 X10 Y260 F9000\n;end extruder_1\n" + "default_value": "\nM104 T1 S160\nG91\nG1 Z5 F900\nG90\nG1 X1 Y260 F9000\n;end extruder_1\n" } } } diff --git a/resources/extruders/cartesio_extruder_2.def.json b/resources/extruders/cartesio_extruder_2.def.json index 0a2cc072f9..ce07502943 100644 --- a/resources/extruders/cartesio_extruder_2.def.json +++ b/resources/extruders/cartesio_extruder_2.def.json @@ -19,7 +19,7 @@ "default_value": "\n;start extruder_2\n\nM117 printing\n" }, "machine_extruder_end_code": { - "default_value": "\nM104 T2 S160\nG91\nG1 Z0.5 F900\nG90\nG1 X10 Y260 F9000\n;end extruder_2\n" + "default_value": "\nM104 T2 S160\nG91\nG1 Z5 F900\nG90\nG1 X1 Y260 F9000\n;end extruder_2\n" } } } diff --git a/resources/extruders/cartesio_extruder_3.def.json b/resources/extruders/cartesio_extruder_3.def.json index 691ef5935b..dea3467daa 100644 --- a/resources/extruders/cartesio_extruder_3.def.json +++ b/resources/extruders/cartesio_extruder_3.def.json @@ -19,7 +19,7 @@ "default_value": "\n;start extruder_3\n\nM117 printing\n" }, "machine_extruder_end_code": { - "default_value": "\nM104 T3 S160\nG91\nG1 Z0.5 F900\nG90\nG1 X10 Y260 F9000\n;end extruder_3\n" + "default_value": "\nM104 T3 S160\nG91\nG1 Z5 F900\nG90\nG1 X1 Y260 F9000\n;end extruder_3\n" } } } diff --git a/resources/extruders/custom_extruder_1.def.json b/resources/extruders/custom_extruder_1.def.json new file mode 100644 index 0000000000..859c6a2f22 --- /dev/null +++ b/resources/extruders/custom_extruder_1.def.json @@ -0,0 +1,17 @@ +{ + "id": "custom_extruder_1", + "version": 2, + "name": "Extruder 1", + "inherits": "fdmextruder", + "metadata": { + "machine": "custom", + "position": "0" + }, + + "overrides": { + "extruder_nr": { + "default_value": 0, + "maximum_value": "7" + } + } +} diff --git a/resources/extruders/custom_extruder_2.def.json b/resources/extruders/custom_extruder_2.def.json new file mode 100644 index 0000000000..eecda5dfcd --- /dev/null +++ b/resources/extruders/custom_extruder_2.def.json @@ -0,0 +1,17 @@ +{ + "id": "custom_extruder_2", + "version": 2, + "name": "Extruder 2", + "inherits": "fdmextruder", + "metadata": { + "machine": "custom", + "position": "1" + }, + + "overrides": { + "extruder_nr": { + "default_value": 1, + "maximum_value": "7" + } + } +} diff --git a/resources/extruders/custom_extruder_3.def.json b/resources/extruders/custom_extruder_3.def.json new file mode 100644 index 0000000000..77909ec05d --- /dev/null +++ b/resources/extruders/custom_extruder_3.def.json @@ -0,0 +1,17 @@ +{ + "id": "custom_extruder_3", + "version": 2, + "name": "Extruder 3", + "inherits": "fdmextruder", + "metadata": { + "machine": "custom", + "position": "2" + }, + + "overrides": { + "extruder_nr": { + "default_value": 2, + "maximum_value": "7" + } + } +} diff --git a/resources/extruders/custom_extruder_4.def.json b/resources/extruders/custom_extruder_4.def.json new file mode 100644 index 0000000000..be792c3a8e --- /dev/null +++ b/resources/extruders/custom_extruder_4.def.json @@ -0,0 +1,17 @@ +{ + "id": "custom_extruder_4", + "version": 2, + "name": "Extruder 4", + "inherits": "fdmextruder", + "metadata": { + "machine": "custom", + "position": "3" + }, + + "overrides": { + "extruder_nr": { + "default_value": 3, + "maximum_value": "7" + } + } +} diff --git a/resources/extruders/custom_extruder_5.def.json b/resources/extruders/custom_extruder_5.def.json new file mode 100644 index 0000000000..ea64605041 --- /dev/null +++ b/resources/extruders/custom_extruder_5.def.json @@ -0,0 +1,17 @@ +{ + "id": "custom_extruder_5", + "version": 2, + "name": "Extruder 5", + "inherits": "fdmextruder", + "metadata": { + "machine": "custom", + "position": "4" + }, + + "overrides": { + "extruder_nr": { + "default_value": 4, + "maximum_value": "7" + } + } +} diff --git a/resources/extruders/custom_extruder_6.def.json b/resources/extruders/custom_extruder_6.def.json new file mode 100644 index 0000000000..fd27fadace --- /dev/null +++ b/resources/extruders/custom_extruder_6.def.json @@ -0,0 +1,17 @@ +{ + "id": "custom_extruder_6", + "version": 2, + "name": "Extruder 6", + "inherits": "fdmextruder", + "metadata": { + "machine": "custom", + "position": "5" + }, + + "overrides": { + "extruder_nr": { + "default_value": 5, + "maximum_value": "7" + } + } +} diff --git a/resources/extruders/custom_extruder_7.def.json b/resources/extruders/custom_extruder_7.def.json new file mode 100644 index 0000000000..cf003d1a6b --- /dev/null +++ b/resources/extruders/custom_extruder_7.def.json @@ -0,0 +1,17 @@ +{ + "id": "custom_extruder_7", + "version": 2, + "name": "Extruder 7", + "inherits": "fdmextruder", + "metadata": { + "machine": "custom", + "position": "6" + }, + + "overrides": { + "extruder_nr": { + "default_value": 6, + "maximum_value": "7" + } + } +} diff --git a/resources/extruders/custom_extruder_8.def.json b/resources/extruders/custom_extruder_8.def.json new file mode 100644 index 0000000000..f418a55186 --- /dev/null +++ b/resources/extruders/custom_extruder_8.def.json @@ -0,0 +1,17 @@ +{ + "id": "custom_extruder_8", + "version": 2, + "name": "Extruder 8", + "inherits": "fdmextruder", + "metadata": { + "machine": "custom", + "position": "7" + }, + + "overrides": { + "extruder_nr": { + "default_value": 7, + "maximum_value": "7" + } + } +} diff --git a/resources/meshes/FT-5_build_plate.stl b/resources/meshes/FT-5_build_plate.stl index 2891632d5f..009eebbfb5 100644 Binary files a/resources/meshes/FT-5_build_plate.stl and b/resources/meshes/FT-5_build_plate.stl differ diff --git a/resources/meshes/kossel_platform.stl b/resources/meshes/kossel_platform.stl index c06cd3a1c6..f6503be5e3 100644 Binary files a/resources/meshes/kossel_platform.stl and b/resources/meshes/kossel_platform.stl differ diff --git a/resources/meshes/kossel_pro_build_platform.stl b/resources/meshes/kossel_pro_build_platform.stl index 8188f0408f..31dcea7dc8 100644 Binary files a/resources/meshes/kossel_pro_build_platform.stl and b/resources/meshes/kossel_pro_build_platform.stl differ diff --git a/resources/meshes/makeR_prusa_tairona_i3_platform.stl b/resources/meshes/makeR_prusa_tairona_i3_platform.stl index 2e4b650637..d6e2b92a8e 100644 Binary files a/resources/meshes/makeR_prusa_tairona_i3_platform.stl and b/resources/meshes/makeR_prusa_tairona_i3_platform.stl differ diff --git a/resources/meshes/mendel90_platform.stl b/resources/meshes/mendel90_platform.stl index 706c90539d..e9cccea41a 100644 Binary files a/resources/meshes/mendel90_platform.stl and b/resources/meshes/mendel90_platform.stl differ diff --git a/resources/meshes/printrbot_simple_metal_upgrade.stl b/resources/meshes/printrbot_simple_metal_upgrade.stl index 3ff934c478..4df4ee6217 100644 Binary files a/resources/meshes/printrbot_simple_metal_upgrade.stl and b/resources/meshes/printrbot_simple_metal_upgrade.stl differ diff --git a/resources/meshes/prusai3_platform.stl b/resources/meshes/prusai3_platform.stl index 2e4b650637..d6e2b92a8e 100644 Binary files a/resources/meshes/prusai3_platform.stl and b/resources/meshes/prusai3_platform.stl differ diff --git a/resources/meshes/prusai3_xl_platform.stl b/resources/meshes/prusai3_xl_platform.stl index c4b8dd1c99..ad66835c06 100644 Binary files a/resources/meshes/prusai3_xl_platform.stl and b/resources/meshes/prusai3_xl_platform.stl differ diff --git a/resources/qml/Actions.qml b/resources/qml/Actions.qml index b9eef11a55..3169f4f170 100755 --- a/resources/qml/Actions.qml +++ b/resources/qml/Actions.qml @@ -205,6 +205,7 @@ Item text: catalog.i18ncp("@action:inmenu menubar:edit", "Multiply Selected Model", "Multiply Selected Models", UM.Selection.selectionCount); enabled: UM.Controller.toolsEnabled && UM.Selection.hasSelection; iconName: "edit-duplicate"; + shortcut: "Ctrl+M" } Action diff --git a/resources/qml/AddMachineDialog.qml b/resources/qml/AddMachineDialog.qml index ba3f40260d..756badc4d2 100644 --- a/resources/qml/AddMachineDialog.qml +++ b/resources/qml/AddMachineDialog.qml @@ -180,7 +180,7 @@ UM.Dialog anchors.bottom:parent.bottom spacing: UM.Theme.getSize("default_margin").width - Text + Label { text: catalog.i18nc("@label", "Printer Name:") anchors.verticalCenter: machineName.verticalCenter diff --git a/resources/qml/DiscardOrKeepProfileChangesDialog.qml b/resources/qml/DiscardOrKeepProfileChangesDialog.qml index 4233bb9e18..1cdde17840 100644 --- a/resources/qml/DiscardOrKeepProfileChangesDialog.qml +++ b/resources/qml/DiscardOrKeepProfileChangesDialog.qml @@ -14,8 +14,8 @@ UM.Dialog id: base title: catalog.i18nc("@title:window", "Discard or Keep changes") - width: 800 * Screen.devicePixelRatio - height: 400 * Screen.devicePixelRatio + width: 800 + height: 400 property var changesModel: Cura.UserChangesModel{ id: userChangesModel} onVisibilityChanged: { @@ -36,9 +36,14 @@ UM.Dialog } } - Column + Row { - anchors.fill: parent + id: infoTextRow + height: childrenRect.height + anchors.margins: UM.Theme.getSize("default_margin").width + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top spacing: UM.Theme.getSize("default_margin").width UM.I18nCatalog @@ -47,29 +52,26 @@ UM.Dialog name: "cura" } - Row + Label { - height: childrenRect.height + text: catalog.i18nc("@text:window", "You have customized some profile settings.\nWould you like to keep or discard those settings?") anchors.margins: UM.Theme.getSize("default_margin").width - anchors.left: parent.left - anchors.right: parent.right - spacing: UM.Theme.getSize("default_margin").width - - Label - { - text: catalog.i18nc("@text:window", "You have customized some profile settings.\nWould you like to keep or discard those settings?") - anchors.margins: UM.Theme.getSize("default_margin").width - font: UM.Theme.getFont("default") - wrapMode: Text.WordWrap - } + font: UM.Theme.getFont("default") + wrapMode: Text.WordWrap } + } + Item + { + anchors.margins: UM.Theme.getSize("default_margin").width + anchors.top: infoTextRow.bottom + anchors.bottom: optionRow.top + anchors.left: parent.left + anchors.right: parent.right TableView { - anchors.margins: UM.Theme.getSize("default_margin").width - anchors.left: parent.left - anchors.right: parent.right - height: base.height - 150 * Screen.devicePixelRatio + anchors.fill: parent + height: base.height - 150 id: tableView Component { @@ -132,92 +134,96 @@ UM.Dialog model: base.changesModel } + } - Item + Item + { + id: optionRow + anchors.bottom: buttonsRow.top + anchors.right: parent.right + anchors.left: parent.left + anchors.margins: UM.Theme.getSize("default_margin").width + height: childrenRect.height + + ComboBox { - anchors.right: parent.right - anchors.left: parent.left - anchors.margins: UM.Theme.getSize("default_margin").width - height:childrenRect.height + id: discardOrKeepProfileChangesDropDownButton + width: 300 - ComboBox + model: ListModel { - id: discardOrKeepProfileChangesDropDownButton - width: 300 + id: discardOrKeepProfileListModel - model: ListModel - { - id: discardOrKeepProfileListModel - - Component.onCompleted: { - append({ text: catalog.i18nc("@option:discardOrKeep", "Always ask me this"), code: "always_ask" }) - append({ text: catalog.i18nc("@option:discardOrKeep", "Discard and never ask again"), code: "always_discard" }) - append({ text: catalog.i18nc("@option:discardOrKeep", "Keep and never ask again"), code: "always_keep" }) - } - } - - onActivated: - { - var code = model.get(index).code; - UM.Preferences.setValue("cura/choice_on_profile_override", code); - - if (code == "always_keep") { - keepButton.enabled = true; - discardButton.enabled = false; - } - else if (code == "always_discard") { - keepButton.enabled = false; - discardButton.enabled = true; - } - else { - keepButton.enabled = true; - discardButton.enabled = true; - } - } - } - } - - Item - { - anchors.right: parent.right - anchors.left: parent.left - anchors.margins: UM.Theme.getSize("default_margin").width - height: childrenRect.height - - Button - { - id: discardButton - text: catalog.i18nc("@action:button", "Discard"); - anchors.right: parent.right - onClicked: - { - CuraApplication.discardOrKeepProfileChangesClosed("discard") - base.hide() - } - isDefault: true - } - - Button - { - id: keepButton - text: catalog.i18nc("@action:button", "Keep"); - anchors.right: discardButton.left - anchors.rightMargin: UM.Theme.getSize("default_margin").width - onClicked: - { - CuraApplication.discardOrKeepProfileChangesClosed("keep") - base.hide() + Component.onCompleted: { + append({ text: catalog.i18nc("@option:discardOrKeep", "Always ask me this"), code: "always_ask" }) + append({ text: catalog.i18nc("@option:discardOrKeep", "Discard and never ask again"), code: "always_discard" }) + append({ text: catalog.i18nc("@option:discardOrKeep", "Keep and never ask again"), code: "always_keep" }) } } - Button + onActivated: { - id: createNewProfileButton - text: catalog.i18nc("@action:button", "Create New Profile"); - anchors.left: parent.left - action: Cura.Actions.addProfile - onClicked: base.hide() + var code = model.get(index).code; + UM.Preferences.setValue("cura/choice_on_profile_override", code); + + if (code == "always_keep") { + keepButton.enabled = true; + discardButton.enabled = false; + } + else if (code == "always_discard") { + keepButton.enabled = false; + discardButton.enabled = true; + } + else { + keepButton.enabled = true; + discardButton.enabled = true; + } } } } + + Item + { + id: buttonsRow + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.left + anchors.margins: UM.Theme.getSize("default_margin").width + height: childrenRect.height + + Button + { + id: discardButton + text: catalog.i18nc("@action:button", "Discard"); + anchors.right: parent.right + onClicked: + { + CuraApplication.discardOrKeepProfileChangesClosed("discard") + base.hide() + } + isDefault: true + } + + Button + { + id: keepButton + text: catalog.i18nc("@action:button", "Keep"); + anchors.right: discardButton.left + anchors.rightMargin: UM.Theme.getSize("default_margin").width + onClicked: + { + CuraApplication.discardOrKeepProfileChangesClosed("keep") + base.hide() + } + } + + Button + { + id: createNewProfileButton + text: catalog.i18nc("@action:button", "Create New Profile"); + anchors.left: parent.left + action: Cura.Actions.addProfile + onClicked: base.hide() + } + } } \ No newline at end of file diff --git a/resources/qml/ExtruderButton.qml b/resources/qml/ExtruderButton.qml new file mode 100644 index 0000000000..ba503dba2b --- /dev/null +++ b/resources/qml/ExtruderButton.qml @@ -0,0 +1,80 @@ +// Copyright (c) 2017 Ultimaker B.V. +// Cura is released under the terms of the AGPLv3 or higher. + +import QtQuick 2.2 +import QtQuick.Controls 1.1 + +import UM 1.2 as UM +import Cura 1.0 as Cura + +Button +{ + id: base + + property var extruder; + + text: catalog.i18ncp("@label", "Print Selected Model with %1", "Print Selected Models With %1", UM.Selection.selectionCount).arg(extruder.name) + + style: UM.Theme.styles.tool_button; + iconSource: checked ? UM.Theme.getIcon("material_selected") : UM.Theme.getIcon("material_not_selected"); + + checked: ExtruderManager.selectedObjectExtruders.indexOf(extruder.id) != -1 + enabled: UM.Selection.hasSelection + + property color customColor: base.hovered ? UM.Theme.getColor("button_hover") : UM.Theme.getColor("button"); + + Rectangle + { + anchors.fill: parent + anchors.margins: UM.Theme.getSize("default_lining").width; + + color: "transparent" + + border.width: base.checked ? UM.Theme.getSize("default_lining").width : 0; + border.color: UM.Theme.getColor("button_text") + } + + Item + { + anchors + { + right: parent.right; + top: parent.top; + margins: UM.Theme.getSize("default_lining").width * 3 + } + width: UM.Theme.getSize("default_margin").width + height: UM.Theme.getSize("default_margin").height + + Text + { + anchors.centerIn: parent; + text: index + 1; + color: parent.enabled ? UM.Theme.getColor("button_text") : UM.Theme.getColor("button_disabled_text") + font: UM.Theme.getFont("default_bold"); + } + } + + Rectangle + { + anchors + { + left: parent.left; + top: parent.top; + margins: UM.Theme.getSize("default_lining").width * 3 + } + + color: model.color + + width: UM.Theme.getSize("default_margin").width + height: UM.Theme.getSize("default_margin").height + + border.width: UM.Theme.getSize("default_lining").width + border.color: UM.Theme.getColor("lining"); + } + + onClicked: + { + forceActiveFocus() //First grab focus, so all the text fields are updated + CuraActions.setExtruderForSelection(extruder.id); + } +} diff --git a/resources/qml/JobSpecs.qml b/resources/qml/JobSpecs.qml index 54b559f794..13d855d993 100644 --- a/resources/qml/JobSpecs.qml +++ b/resources/qml/JobSpecs.qml @@ -24,6 +24,7 @@ Item { UM.I18nCatalog { id: catalog; name:"cura"} property variant printDuration: PrintInformation.currentPrintTime + property variant printDurationPerFeature: PrintInformation.printTimesPerFeature property variant printMaterialLengths: PrintInformation.materialLengths property variant printMaterialWeights: PrintInformation.materialWeights property variant printMaterialCosts: PrintInformation.materialCosts @@ -159,7 +160,7 @@ Item { UM.RecolorImage { id: timeIcon - anchors.right: timeSpec.left + anchors.right: timeSpecPerFeatureTooltipArea.left anchors.rightMargin: UM.Theme.getSize("default_margin").width/2 anchors.verticalCenter: parent.verticalCenter width: UM.Theme.getSize("save_button_specs_icons").width @@ -169,15 +170,50 @@ Item { color: UM.Theme.getColor("text_subtext") source: UM.Theme.getIcon("print_time") } - Text + UM.TooltipArea { - id: timeSpec + id: timeSpecPerFeatureTooltipArea + text: { + var order = ["inset_0", "inset_x", "skin", "infill", "support_infill", "support_interface", "support", "travel", "retract", "none"]; + var visible_names = { + "inset_0": catalog.i18nc("@tooltip", "Outer Wall"), + "inset_x": catalog.i18nc("@tooltip", "Inner Walls"), + "skin": catalog.i18nc("@tooltip", "Skin"), + "infill": catalog.i18nc("@tooltip", "Infill"), + "support_infill": catalog.i18nc("@tooltip", "Support Infill"), + "support_interface": catalog.i18nc("@tooltip", "Support Interface"), + "support": catalog.i18nc("@tooltip", "Support"), + "travel": catalog.i18nc("@tooltip", "Travel"), + "retract": catalog.i18nc("@tooltip", "Retractions"), + "none": catalog.i18nc("@tooltip", "Other") + }; + var result = ""; + for(var feature in order) + { + feature = order[feature]; + if(base.printDurationPerFeature[feature] && base.printDurationPerFeature[feature].totalSeconds > 0) + { + result += "
" + visible_names[feature] + ": " + base.printDurationPerFeature[feature].getDisplayString(UM.DurationFormat.Short); + } + } + result = result.replace(/^\/, ""); // remove newline before first item + return result; + } + width: childrenRect.width + height: childrenRect.height anchors.right: lengthIcon.left anchors.rightMargin: UM.Theme.getSize("default_margin").width anchors.verticalCenter: parent.verticalCenter - font: UM.Theme.getFont("small") - color: UM.Theme.getColor("text_subtext") - text: (!base.printDuration || !base.printDuration.valid) ? catalog.i18nc("@label", "00h 00min") : base.printDuration.getDisplayString(UM.DurationFormat.Short) + + Text + { + id: timeSpec + anchors.left: parent.left + anchors.top: parent.top + font: UM.Theme.getFont("small") + color: UM.Theme.getColor("text_subtext") + text: (!base.printDuration || !base.printDuration.valid) ? catalog.i18nc("@label", "00h 00min") : base.printDuration.getDisplayString(UM.DurationFormat.Short) + } } UM.RecolorImage { @@ -212,8 +248,9 @@ Item { { lengths.push(base.printMaterialLengths[index].toFixed(2)); weights.push(String(Math.floor(base.printMaterialWeights[index]))); - costs.push(base.printMaterialCosts[index].toFixed(2)); - if(base.printMaterialCosts[index] > 0) + var cost = base.printMaterialCosts[index] == undefined ? 0 : base.printMaterialCosts[index].toFixed(2); + costs.push(cost); + if(cost > 0) { someCostsKnown = true; } diff --git a/resources/qml/Menus/ContextMenu.qml b/resources/qml/Menus/ContextMenu.qml old mode 100644 new mode 100755 index 8d1a6dc02c..0c0dbc39ca --- a/resources/qml/Menus/ContextMenu.qml +++ b/resources/qml/Menus/ContextMenu.qml @@ -93,6 +93,11 @@ Menu copiesField.focus = true; } + onVisibleChanged: + { + copiesField.forceActiveFocus(); + } + standardButtons: StandardButton.Ok | StandardButton.Cancel Row @@ -108,6 +113,7 @@ Menu SpinBox { id: copiesField + focus: true minimumValue: 1 maximumValue: 99 } diff --git a/resources/qml/Menus/ProfileMenu.qml b/resources/qml/Menus/ProfileMenu.qml index 9dea8420bb..4a2908277e 100644 --- a/resources/qml/Menus/ProfileMenu.qml +++ b/resources/qml/Menus/ProfileMenu.qml @@ -19,7 +19,7 @@ Menu { text: model.name + " - " + model.layer_height checkable: true - checked: Cura.MachineManager.activeQualityChangesId == "empty_quality_changes" && Cura.MachineManager.activeQualityType == model.metadata.quality_type + checked: Cura.MachineManager.activeQualityChangesId == "" && Cura.MachineManager.activeQualityType == model.metadata.quality_type exclusiveGroup: group onTriggered: Cura.MachineManager.setActiveQuality(model.id) } diff --git a/resources/qml/Preferences/GeneralPage.qml b/resources/qml/Preferences/GeneralPage.qml index 214e5aea8f..f825cfc08c 100755 --- a/resources/qml/Preferences/GeneralPage.qml +++ b/resources/qml/Preferences/GeneralPage.qml @@ -25,6 +25,17 @@ UM.PreferencesPage } } + function setDefaultTheme(defaultThemeCode) + { + for(var i = 0; i < themeList.count; i++) + { + if (themeComboBox.model.get(i).code == defaultThemeCode) + { + themeComboBox.currentIndex = i + } + } + } + function setDefaultDiscardOrKeepProfile(code) { for (var i = 0; i < choiceOnProfileOverrideDropDownButton.model.count; i++) @@ -55,6 +66,10 @@ UM.PreferencesPage var defaultLanguage = UM.Preferences.getValue("general/language") setDefaultLanguage(defaultLanguage) + UM.Preferences.resetPreference("general/theme") + var defaultTheme = UM.Preferences.getValue("general/theme") + setDefaultTheme(defaultTheme) + UM.Preferences.resetPreference("physics/automatic_push_free") pushFreeCheckbox.checked = boolCheck(UM.Preferences.getValue("physics/automatic_push_free")) UM.Preferences.resetPreference("physics/automatic_drop_down") @@ -111,9 +126,11 @@ UM.PreferencesPage text: catalog.i18nc("@label","Interface") } - Row + GridLayout { - spacing: UM.Theme.getSize("default_margin").width + id: interfaceGrid + columns: 4 + Label { id: languageLabel @@ -174,22 +191,75 @@ UM.PreferencesPage { id: currencyLabel text: catalog.i18nc("@label","Currency:") - anchors.verticalCenter: languageComboBox.verticalCenter + anchors.verticalCenter: currencyField.verticalCenter } + TextField { id: currencyField text: UM.Preferences.getValue("cura/currency") onTextChanged: UM.Preferences.setValue("cura/currency", text) } + + Label + { + id: themeLabel + text: catalog.i18nc("@label","Theme:") + anchors.verticalCenter: themeComboBox.verticalCenter + } + + ComboBox + { + id: themeComboBox + + model: ListModel + { + id: themeList + + Component.onCompleted: { + append({ text: catalog.i18nc("@item:inlistbox", "Ultimaker"), code: "cura" }) + } + } + + currentIndex: + { + var code = UM.Preferences.getValue("general/theme"); + for(var i = 0; i < themeList.count; ++i) + { + if(model.get(i).code == code) + { + return i + } + } + } + onActivated: UM.Preferences.setValue("general/theme", model.get(index).code) + + Component.onCompleted: + { + // Because ListModel is stupid and does not allow using qsTr() for values. + for(var i = 0; i < themeList.count; ++i) + { + themeList.setProperty(i, "text", catalog.i18n(themeList.get(i).text)); + } + + // Glorious hack time. ComboBox does not update the text properly after changing the + // model. So change the indices around to force it to update. + currentIndex += 1; + currentIndex -= 1; + } + + } } - Label + + + + Label { id: languageCaption //: Language change warning - text: catalog.i18nc("@label", "You will need to restart the application for language changes to have effect.") + text: catalog.i18nc("@label", "You will need to restart the application for these changes to have effect.") wrapMode: Text.WordWrap font.italic: true } @@ -211,14 +281,13 @@ UM.PreferencesPage CheckBox { id: autoSliceCheckbox - checked: boolCheck(UM.Preferences.getValue("general/auto_slice")) onClicked: UM.Preferences.setValue("general/auto_slice", checked) text: catalog.i18nc("@option:check","Slice automatically"); } } - + Item { //: Spacer @@ -253,7 +322,7 @@ UM.PreferencesPage UM.TooltipArea { width: childrenRect.width; height: childrenRect.height; - text: catalog.i18nc("@info:tooltip","Moves the camera so the model is in the center of the view when an model is selected") + text: catalog.i18nc("@info:tooltip","Moves the camera so the model is in the center of the view when a model is selected") CheckBox { diff --git a/resources/qml/Settings/SettingView.qml b/resources/qml/Settings/SettingView.qml index fcd1523c15..85d19b0cf0 100644 --- a/resources/qml/Settings/SettingView.qml +++ b/resources/qml/Settings/SettingView.qml @@ -154,7 +154,7 @@ Item id: definitionsModel; containerId: Cura.MachineManager.activeDefinitionId visibilityHandler: UM.SettingPreferenceVisibilityHandler { } - exclude: ["machine_settings", "command_line_settings", "infill_mesh", "infill_mesh_order", "support_mesh", "anti_overhang_mesh"] // TODO: infill_mesh settigns are excluded hardcoded, but should be based on the fact that settable_globally, settable_per_meshgroup and settable_per_extruder are false. + exclude: ["machine_settings", "command_line_settings", "infill_mesh", "infill_mesh_order", "cutting_mesh", "support_mesh", "anti_overhang_mesh"] // TODO: infill_mesh settigns are excluded hardcoded, but should be based on the fact that settable_globally, settable_per_meshgroup and settable_per_extruder are false. expanded: CuraApplication.expandedCategories onExpandedChanged: { @@ -179,7 +179,7 @@ Item Behavior on opacity { NumberAnimation { duration: 100 } } enabled: { - if(!ExtruderManager.activeExtruderStackId && ExtruderManager.extruderCount > 0) + if(!ExtruderManager.activeExtruderStackId && machineExtruderCount.properties.value > 1) { // disable all controls on the global tab, except categories return model.type == "category" diff --git a/resources/qml/Sidebar.qml b/resources/qml/Sidebar.qml index ba5106c767..ec187cef91 100755 --- a/resources/qml/Sidebar.qml +++ b/resources/qml/Sidebar.qml @@ -348,6 +348,7 @@ Rectangle Rectangle { id: settingsModeSelection + color: "transparent" width: parent.width * 0.55 height: UM.Theme.getSize("sidebar_header_mode_toggle").height anchors.right: parent.right @@ -408,18 +409,34 @@ Rectangle } ExclusiveGroup { id: modeMenuGroup; } - Text + Label { id: toggleLeftText anchors.right: modeToggleSwitch.left - anchors.rightMargin: UM.Theme.getSize("toggle_button_text_anchoring_margin").width + anchors.rightMargin: UM.Theme.getSize("default_margin").width anchors.verticalCenter: parent.verticalCenter text: "" - color: UM.Theme.getColor("toggle_active_text") + color: + { + if(toggleLeftTextMouseArea.containsMouse) + { + return UM.Theme.getColor("mode_switch_text_hover"); + } + else if(!modeToggleSwitch.checked) + { + return UM.Theme.getColor("mode_switch_text_checked"); + } + else + { + return UM.Theme.getColor("mode_switch_text"); + } + } font: UM.Theme.getFont("default") MouseArea { + id: toggleLeftTextMouseArea + hoverEnabled: true anchors.fill: parent onClicked: { @@ -438,10 +455,20 @@ Rectangle id: modeToggleSwitch checked: false anchors.right: toggleRightText.left - anchors.rightMargin: UM.Theme.getSize("toggle_button_text_anchoring_margin").width + anchors.rightMargin: UM.Theme.getSize("default_margin").width anchors.verticalCenter: parent.verticalCenter - onClicked: + property bool _hovered: modeToggleSwitchMouseArea.containsMouse || toggleLeftTextMouseArea.containsMouse || toggleRightTextMouseArea.containsMouse + + MouseArea + { + id: modeToggleSwitchMouseArea + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.NoButton + } + + onCheckedChanged: { var index = 0; if (checked) @@ -457,20 +484,36 @@ Rectangle UM.Preferences.setValue("cura/active_mode", index); } - style: UM.Theme.styles.toggle_button + style: UM.Theme.styles.mode_switch } - Text + Label { id: toggleRightText anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter text: "" - color: UM.Theme.getColor("toggle_active_text") + color: + { + if(toggleRightTextMouseArea.containsMouse) + { + return UM.Theme.getColor("mode_switch_text_hover"); + } + else if(modeToggleSwitch.checked) + { + return UM.Theme.getColor("mode_switch_text_checked"); + } + else + { + return UM.Theme.getColor("mode_switch_text"); + } + } font: UM.Theme.getFont("default") MouseArea { + id: toggleRightTextMouseArea + hoverEnabled: true anchors.fill: parent onClicked: { diff --git a/resources/qml/SidebarSimple.qml b/resources/qml/SidebarSimple.qml index 8f43e411ff..3eaf0797e8 100644 --- a/resources/qml/SidebarSimple.qml +++ b/resources/qml/SidebarSimple.qml @@ -19,7 +19,7 @@ Item property Action configureSettings; property variant minimumPrintTime: PrintInformation.minimumPrintTime; property variant maximumPrintTime: PrintInformation.maximumPrintTime; - property bool settingsEnabled: ExtruderManager.activeExtruderStackId || ExtruderManager.extruderCount == 0 + property bool settingsEnabled: ExtruderManager.activeExtruderStackId || machineExtruderCount.properties.value == 1 Component.onCompleted: PrintInformation.enabled = true Component.onDestruction: PrintInformation.enabled = false @@ -30,6 +30,7 @@ Item id: infillCellLeft anchors.top: parent.top anchors.left: parent.left + anchors.topMargin: UM.Theme.getSize("default_margin").height width: base.width * .45 - UM.Theme.getSize("default_margin").width height: childrenRect.height @@ -47,12 +48,13 @@ Item } } - Flow + Row { id: infillCellRight height: childrenRect.height; - width: base.width * .55 + width: base.width * .5 + spacing: UM.Theme.getSize("default_margin").width anchors.left: infillCellLeft.right @@ -63,10 +65,11 @@ Item id: infillListView property int activeIndex: { - var density = parseInt(infillDensity.properties.value) + var density = parseInt(infillDensity.properties.value); + var steps = parseInt(infillSteps.properties.value); for(var i = 0; i < infillModel.count; ++i) { - if(density > infillModel.get(i).percentageMin && density <= infillModel.get(i).percentageMax ) + if(density > infillModel.get(i).percentageMin && density <= infillModel.get(i).percentageMax && steps > infillModel.get(i).stepsMin && steps <= infillModel.get(i).stepsMax) { return i; } @@ -85,7 +88,7 @@ Item { id: infillIconLining - width: (infillCellRight.width - 3 * UM.Theme.getSize("default_margin").width) / 4; + width: (infillCellRight.width - ((infillModel.count - 1) * UM.Theme.getSize("default_margin").width)) / (infillModel.count); height: width border.color: @@ -150,6 +153,7 @@ Item if (infillListView.activeIndex != index) { infillDensity.setPropertyValue("value", model.percentage) + infillSteps.setPropertyValue("value", model.steps) } } onEntered: @@ -181,37 +185,61 @@ Item Component.onCompleted: { infillModel.append({ - name: catalog.i18nc("@label", "Hollow"), + name: catalog.i18nc("@label", "Empty"), percentage: 0, + steps: 0, percentageMin: -1, percentageMax: 0, - text: catalog.i18nc("@label", "No (0%) infill will leave your model hollow at the cost of low strength"), + stepsMin: -1, + stepsMax: 0, + text: catalog.i18nc("@label", "Empty infill will leave your model hollow with low strength."), icon: "hollow" }) infillModel.append({ name: catalog.i18nc("@label", "Light"), percentage: 20, + steps: 0, percentageMin: 0, percentageMax: 30, - text: catalog.i18nc("@label", "Light (20%) infill will give your model an average strength"), + stepsMin: -1, + stepsMax: 0, + text: catalog.i18nc("@label", "Light (20%) infill will give your model an average strength."), icon: "sparse" }) infillModel.append({ name: catalog.i18nc("@label", "Dense"), percentage: 50, + steps: 0, percentageMin: 30, percentageMax: 70, - text: catalog.i18nc("@label", "Dense (50%) infill will give your model an above average strength"), + stepsMin: -1, + stepsMax: 0, + text: catalog.i18nc("@label", "Dense (50%) infill will give your model an above average strength."), icon: "dense" }) infillModel.append({ name: catalog.i18nc("@label", "Solid"), percentage: 100, + steps: 0, percentageMin: 70, - percentageMax: 100, - text: catalog.i18nc("@label", "Solid (100%) infill will make your model completely solid"), + percentageMax: 9999999999, + stepsMin: -1, + stepsMax: 0, + text: catalog.i18nc("@label", "Solid (100%) infill will make your model completely solid."), icon: "solid" }) + infillModel.append({ + name: catalog.i18nc("@label", "Gradual"), + percentage: 90, + steps: 5, + percentageMin: 0, + percentageMax: 9999999999, + stepsMin: 0, + stepsMax: 9999999999, + infill_layer_height: 1.5, + text: catalog.i18nc("@label", "Gradual infill will gradually increase the amount of infill towards the top."), + icon: "gradual" + }) } } } @@ -220,7 +248,7 @@ Item { id: helpersCell anchors.top: infillCellRight.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height + anchors.topMargin: UM.Theme.getSize("default_margin").height * 2 anchors.left: parent.left anchors.right: parent.right height: childrenRect.height @@ -240,6 +268,8 @@ Item CheckBox { id: enableSupportCheckBox + property alias _hovered: enableSupportMouseArea.containsMouse + anchors.top: parent.top anchors.left: enableSupportLabel.right anchors.leftMargin: UM.Theme.getSize("default_margin").width @@ -390,7 +420,7 @@ Item property alias _hovered: adhesionMouseArea.containsMouse anchors.top: supportExtruderCombobox.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height + anchors.topMargin: UM.Theme.getSize("default_margin").height * 2 anchors.left: adhesionHelperLabel.right anchors.leftMargin: UM.Theme.getSize("default_margin").width @@ -465,7 +495,7 @@ Item { id: tipsCell anchors.top: helpersCell.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height + anchors.topMargin: UM.Theme.getSize("default_margin").height * 2 anchors.left: parent.left width: parent.width height: childrenRect.height @@ -478,7 +508,7 @@ Item anchors.rightMargin: UM.Theme.getSize("default_margin").width wrapMode: Text.WordWrap //: Tips label - text: catalog.i18nc("@label", "Need help improving your prints? Read the Ultimaker Troubleshooting Guides").arg("https://ultimaker.com/en/troubleshooting"); + text: catalog.i18nc("@label", "Need help improving your prints?
Read the Ultimaker Troubleshooting Guides").arg("https://ultimaker.com/en/troubleshooting"); font: UM.Theme.getFont("default"); color: UM.Theme.getColor("text"); linkColor: UM.Theme.getColor("text_link") @@ -496,6 +526,16 @@ Item storeIndex: 0 } + UM.SettingPropertyProvider + { + id: infillSteps + + containerStackId: Cura.MachineManager.activeStackId + key: "gradual_infill_steps" + watchedProperties: [ "value" ] + storeIndex: 0 + } + UM.SettingPropertyProvider { id: platformAdhesionType diff --git a/resources/qml/Toolbar.qml b/resources/qml/Toolbar.qml index 60fc6fd723..5100a0dacb 100644 --- a/resources/qml/Toolbar.qml +++ b/resources/qml/Toolbar.qml @@ -6,28 +6,33 @@ import QtQuick.Controls 1.1 import QtQuick.Controls.Styles 1.1 import QtQuick.Layouts 1.1 -import UM 1.0 as UM +import UM 1.2 as UM +import Cura 1.0 as Cura -Item { +Item +{ id: base; width: buttons.width; height: buttons.height property int activeY - ColumnLayout { + Column + { id: buttons; anchors.bottom: parent.bottom; anchors.left: parent.left; spacing: UM.Theme.getSize("button_lining").width - Repeater { + Repeater + { id: repeat model: UM.ToolModel { } - Button { + Button + { text: model.name iconSource: UM.Theme.getIcon(model.icon); @@ -45,9 +50,11 @@ Item { } //Workaround since using ToolButton"s onClicked would break the binding of the checked property, instead //just catch the click so we do not trigger that behaviour. - MouseArea { + MouseArea + { anchors.fill: parent; - onClicked: { + onClicked: + { forceActiveFocus() //First grab focus, so all the text fields are updated if(parent.checked) { @@ -61,9 +68,19 @@ Item { } } } + + Item { height: UM.Theme.getSize("default_margin").height; width: 1; visible: extruders.count > 0 } + + Repeater + { + id: extruders + model: Cura.ExtrudersModel { id: extrudersModel } + ExtruderButton { extruder: model } + } } - UM.PointingRectangle { + UM.PointingRectangle + { id: panelBorder; anchors.left: parent.right; @@ -75,7 +92,8 @@ Item { target: Qt.point(parent.right, base.activeY + UM.Theme.getSize("button").height/2) arrowSize: UM.Theme.getSize("default_arrow").width - width: { + width: + { if (panel.item && panel.width > 0){ return Math.max(panel.width + 2 * UM.Theme.getSize("default_margin").width) } @@ -90,7 +108,8 @@ Item { color: UM.Theme.getColor("lining"); - UM.PointingRectangle { + UM.PointingRectangle + { id: panelBackground; color: UM.Theme.getColor("tool_panel_background"); @@ -105,7 +124,8 @@ Item { } } - Loader { + Loader + { id: panel x: UM.Theme.getSize("default_margin").width; @@ -116,6 +136,8 @@ Item { } } + // This rectangle displays the information about the current angle etc. when + // dragging a tool handle. Rectangle { x: -base.x + base.mouseX + UM.Theme.getSize("default_margin").width diff --git a/resources/quality/cartesio/abs/cartesio_0.25_abs_high.inst.cfg b/resources/quality/cartesio/abs/cartesio_0.25_abs_high.inst.cfg index c26f4a2683..6d7fe3b0eb 100644 --- a/resources/quality/cartesio/abs/cartesio_0.25_abs_high.inst.cfg +++ b/resources/quality/cartesio/abs/cartesio_0.25_abs_high.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = high -material = generic_abs_cartesio_0.25_mm +material = generic_abs_175_cartesio_0.25_mm weight = 1 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/abs/cartesio_0.25_abs_normal.inst.cfg b/resources/quality/cartesio/abs/cartesio_0.25_abs_normal.inst.cfg index a7c5677980..f59b39a94b 100644 --- a/resources/quality/cartesio/abs/cartesio_0.25_abs_normal.inst.cfg +++ b/resources/quality/cartesio/abs/cartesio_0.25_abs_normal.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = normal -material = generic_abs_cartesio_0.25_mm +material = generic_abs_175_cartesio_0.25_mm weight = 2 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/abs/cartesio_0.4_abs_high.inst.cfg b/resources/quality/cartesio/abs/cartesio_0.4_abs_high.inst.cfg index 1287d66e33..d34b6dfb3b 100644 --- a/resources/quality/cartesio/abs/cartesio_0.4_abs_high.inst.cfg +++ b/resources/quality/cartesio/abs/cartesio_0.4_abs_high.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = high -material = generic_abs_cartesio_0.4_mm +material = generic_abs_175_cartesio_0.4_mm weight = 1 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/abs/cartesio_0.4_abs_normal.inst.cfg b/resources/quality/cartesio/abs/cartesio_0.4_abs_normal.inst.cfg index 62c4e462e7..2780eea101 100644 --- a/resources/quality/cartesio/abs/cartesio_0.4_abs_normal.inst.cfg +++ b/resources/quality/cartesio/abs/cartesio_0.4_abs_normal.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = normal -material = generic_abs_cartesio_0.4_mm +material = generic_abs_175_cartesio_0.4_mm weight = 2 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/abs/cartesio_0.8_abs_coarse.inst.cfg b/resources/quality/cartesio/abs/cartesio_0.8_abs_coarse.inst.cfg index 4ae04132dc..e22b61a456 100644 --- a/resources/quality/cartesio/abs/cartesio_0.8_abs_coarse.inst.cfg +++ b/resources/quality/cartesio/abs/cartesio_0.8_abs_coarse.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = coarse -material = generic_abs_cartesio_0.8_mm +material = generic_abs_175_cartesio_0.8_mm weight = 3 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/abs/cartesio_0.8_abs_extra_coarse.inst.cfg b/resources/quality/cartesio/abs/cartesio_0.8_abs_extra_coarse.inst.cfg index 836c2f8458..602a11457f 100644 --- a/resources/quality/cartesio/abs/cartesio_0.8_abs_extra_coarse.inst.cfg +++ b/resources/quality/cartesio/abs/cartesio_0.8_abs_extra_coarse.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = extra coarse -material = generic_abs_cartesio_0.8_mm +material = generic_abs_175_cartesio_0.8_mm weight = 4 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/abs/cartesio_0.8_abs_high.inst.cfg b/resources/quality/cartesio/abs/cartesio_0.8_abs_high.inst.cfg index f8e6fac996..02e82c81e9 100644 --- a/resources/quality/cartesio/abs/cartesio_0.8_abs_high.inst.cfg +++ b/resources/quality/cartesio/abs/cartesio_0.8_abs_high.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = high -material = generic_abs_cartesio_0.8_mm +material = generic_abs_175_cartesio_0.8_mm weight = 1 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/abs/cartesio_0.8_abs_normal.inst.cfg b/resources/quality/cartesio/abs/cartesio_0.8_abs_normal.inst.cfg index 7aade0c846..b9b66fd4c0 100644 --- a/resources/quality/cartesio/abs/cartesio_0.8_abs_normal.inst.cfg +++ b/resources/quality/cartesio/abs/cartesio_0.8_abs_normal.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = normal -material = generic_abs_cartesio_0.8_mm +material = generic_abs_175_cartesio_0.8_mm weight = 2 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/arnitel/cartesio_0.4_arnitel2045_high.inst.cfg b/resources/quality/cartesio/arnitel/cartesio_0.4_arnitel2045_high.inst.cfg new file mode 100644 index 0000000000..1d20414f7e --- /dev/null +++ b/resources/quality/cartesio/arnitel/cartesio_0.4_arnitel2045_high.inst.cfg @@ -0,0 +1,62 @@ +[general] +version = 2 +name = High Quality +definition = cartesio + +[metadata] +type = quality +quality_type = high +material = dsm_arnitel2045_175_cartesio_0.4_mm +weight = 1 + +[values] +infill_line_width = 0.5 + +wall_thickness = 1.2 +top_bottom_thickness = 0.8 +wall_0_inset = -0.05 +fill_perimeter_gaps = nowhere +travel_compensate_overlapping_walls_enabled = + +infill_sparse_density = 40 +infill_pattern = grid + +material_print_temperature_layer_0 = =material_print_temperature + 5 +material_initial_print_temperature = =material_print_temperature +material_final_print_temperature = =material_print_temperature +retraction_min_travel = =round(line_width * 10) +retraction_prime_speed = 2 +switch_extruder_retraction_amount = 2 +switch_extruder_retraction_speeds = =retraction_speed +switch_extruder_prime_speed = =retraction_prime_speed + +speed_print = 25 +speed_infill = =speed_print +speed_layer_0 = =round(speed_print / 5 * 4) +speed_wall = =round(speed_print / 2) +speed_wall_0 = =10 if speed_wall < 11 else (speed_print / 5 *3) +speed_topbottom = =round(speed_print / 5 * 4) +speed_slowdown_layers = 1 +speed_travel = =round(speed_print if magic_spiralize else 150) +speed_travel_layer_0 = =speed_travel +speed_support_interface = =speed_topbottom +speed_equalize_flow_enabled = True +speed_equalize_flow_max = =speed_print + +acceleration_enabled = True +acceleration_print = 100 +acceleration_travel = 300 +jerk_print = 5 + +retraction_hop_enabled = True +retraction_hop = 1 + +cool_min_layer_time_fan_speed_max = =cool_min_layer_time +cool_min_layer_time = 20 + +skirt_brim_minimal_length = 50 + +coasting_enable = True +coasting_volume = 0.1 +coasting_min_volume = 0.17 +coasting_speed = 90 diff --git a/resources/quality/cartesio/arnitel/cartesio_0.4_arnitel2045_normal.inst.cfg b/resources/quality/cartesio/arnitel/cartesio_0.4_arnitel2045_normal.inst.cfg new file mode 100644 index 0000000000..2437f41b09 --- /dev/null +++ b/resources/quality/cartesio/arnitel/cartesio_0.4_arnitel2045_normal.inst.cfg @@ -0,0 +1,62 @@ +[general] +version = 2 +name = Normal Quality +definition = cartesio + +[metadata] +type = quality +quality_type = normal +material = dsm_arnitel2045_175_cartesio_0.4_mm +weight = 2 + +[values] +infill_line_width = 0.5 + +wall_thickness = 1.2 +top_bottom_thickness = 0.8 +wall_0_inset = -0.05 +fill_perimeter_gaps = nowhere +travel_compensate_overlapping_walls_enabled = + +infill_sparse_density = 40 +infill_pattern = grid + +material_print_temperature_layer_0 = =material_print_temperature + 5 +material_initial_print_temperature = =material_print_temperature +material_final_print_temperature = =material_print_temperature +retraction_min_travel = =round(line_width * 10) +retraction_prime_speed = 2 +switch_extruder_retraction_amount = 2 +switch_extruder_retraction_speeds = =retraction_speed +switch_extruder_prime_speed = =retraction_prime_speed + +speed_print = 25 +speed_infill = =speed_print +speed_layer_0 = =round(speed_print / 5 * 4) +speed_wall = =round(speed_print / 2) +speed_wall_0 = =10 if speed_wall < 11 else (speed_print / 5 *3) +speed_topbottom = =round(speed_print / 5 * 4) +speed_slowdown_layers = 1 +speed_travel = =round(speed_print if magic_spiralize else 150) +speed_travel_layer_0 = =speed_travel +speed_support_interface = =speed_topbottom +speed_equalize_flow_enabled = True +speed_equalize_flow_max = =speed_print + +acceleration_enabled = True +acceleration_print = 100 +acceleration_travel = 300 +jerk_print = 5 + +retraction_hop_enabled = True +retraction_hop = 1 + +cool_min_layer_time_fan_speed_max = =cool_min_layer_time +cool_min_layer_time = 20 + +skirt_brim_minimal_length = 50 + +coasting_enable = True +coasting_volume = 0.1 +coasting_min_volume = 0.17 +coasting_speed = 90 diff --git a/resources/quality/cartesio/cartesio_global_Coarse_Quality.inst.cfg b/resources/quality/cartesio/cartesio_global_Coarse_Quality.inst.cfg index 1d6f7bb930..4ed6a3a28b 100644 --- a/resources/quality/cartesio/cartesio_global_Coarse_Quality.inst.cfg +++ b/resources/quality/cartesio/cartesio_global_Coarse_Quality.inst.cfg @@ -11,15 +11,22 @@ weight = 0 [values] layer_height = 0.4 +layer_height_0 = =layer_height + +skin_angles = [0,90] + +infill_before_walls = False +infill_angles = [0,90] speed_slowdown_layers = 1 retraction_combing = off support_z_distance = 0 -support_xy_distance = 0.5 +support_xy_distance = 1 support_join_distance = 10 support_interface_enable = True +support_interface_pattern = lines adhesion_type = skirt -skirt_gap = 0.5 +skirt_gap = 1 diff --git a/resources/quality/cartesio/cartesio_global_Extra_Coarse_Quality.inst.cfg b/resources/quality/cartesio/cartesio_global_Extra_Coarse_Quality.inst.cfg index 841d63d1dc..a6b9aae464 100644 --- a/resources/quality/cartesio/cartesio_global_Extra_Coarse_Quality.inst.cfg +++ b/resources/quality/cartesio/cartesio_global_Extra_Coarse_Quality.inst.cfg @@ -11,15 +11,22 @@ weight = 0 [values] layer_height = 0.6 +layer_height_0 = =layer_height + +skin_angles = [0,90] + +infill_before_walls = False +infill_angles = [0,90] speed_slowdown_layers = 1 retraction_combing = off support_z_distance = 0 -support_xy_distance = 0.5 +support_xy_distance = 1 support_join_distance = 10 support_interface_enable = True +support_interface_pattern = lines adhesion_type = skirt -skirt_gap = 0.5 +skirt_gap = 1 diff --git a/resources/quality/cartesio/cartesio_global_High_Quality.inst.cfg b/resources/quality/cartesio/cartesio_global_High_Quality.inst.cfg index 363c18d8a2..17638b0e9b 100644 --- a/resources/quality/cartesio/cartesio_global_High_Quality.inst.cfg +++ b/resources/quality/cartesio/cartesio_global_High_Quality.inst.cfg @@ -11,15 +11,23 @@ weight = 0 [values] layer_height = 0.1 +layer_height_0 = =0.2 if min(extruderValues('machine_nozzle_size')) < 0.3 else 0.3 + +skin_angles = [0,90] + +infill_before_walls = False +infill_angles = [0,90] speed_slowdown_layers = 1 retraction_combing = off support_z_distance = 0 -support_xy_distance = 0.5 +support_xy_distance = 1 support_join_distance = 10 support_interface_enable = True +support_interface_height = 0.5 +support_interface_pattern = lines adhesion_type = skirt -skirt_gap = 0.5 +skirt_gap = 1 diff --git a/resources/quality/cartesio/cartesio_global_Normal_Quality.inst.cfg b/resources/quality/cartesio/cartesio_global_Normal_Quality.inst.cfg index 78272e2aef..56c7db0f4a 100644 --- a/resources/quality/cartesio/cartesio_global_Normal_Quality.inst.cfg +++ b/resources/quality/cartesio/cartesio_global_Normal_Quality.inst.cfg @@ -11,15 +11,22 @@ weight = 0 [values] layer_height = 0.2 +layer_height_0 = =0.2 if min(extruderValues('machine_nozzle_size')) < 0.3 else 0.3 + +skin_angles = [0,90] + +infill_before_walls = False +infill_angles = [0,90] speed_slowdown_layers = 1 retraction_combing = off support_z_distance = 0 -support_xy_distance = 0.5 +support_xy_distance = 1 support_join_distance = 10 support_interface_enable = True +support_interface_pattern = lines adhesion_type = skirt -skirt_gap = 0.5 +skirt_gap = 1 diff --git a/resources/quality/cartesio/hips/cartesio_0.25_hips_high.inst.cfg b/resources/quality/cartesio/hips/cartesio_0.25_hips_high.inst.cfg index 1457945bdb..ed1d1ae00d 100644 --- a/resources/quality/cartesio/hips/cartesio_0.25_hips_high.inst.cfg +++ b/resources/quality/cartesio/hips/cartesio_0.25_hips_high.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = high -material = generic_hips_cartesio_0.25_mm +material = generic_hips_175_cartesio_0.25_mm weight = 1 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/hips/cartesio_0.25_hips_normal.inst.cfg b/resources/quality/cartesio/hips/cartesio_0.25_hips_normal.inst.cfg index ac324cf42c..f259d31022 100644 --- a/resources/quality/cartesio/hips/cartesio_0.25_hips_normal.inst.cfg +++ b/resources/quality/cartesio/hips/cartesio_0.25_hips_normal.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = normal -material = generic_hips_cartesio_0.25_mm +material = generic_hips_175_cartesio_0.25_mm weight = 2 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/hips/cartesio_0.4_hips_high.inst.cfg b/resources/quality/cartesio/hips/cartesio_0.4_hips_high.inst.cfg index 4f95cd2b8b..f3d3fb29c4 100644 --- a/resources/quality/cartesio/hips/cartesio_0.4_hips_high.inst.cfg +++ b/resources/quality/cartesio/hips/cartesio_0.4_hips_high.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = high -material = generic_hips_cartesio_0.4_mm +material = generic_hips_175_cartesio_0.4_mm weight = 1 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/hips/cartesio_0.4_hips_normal.inst.cfg b/resources/quality/cartesio/hips/cartesio_0.4_hips_normal.inst.cfg index ac4de67c0c..96217fa849 100644 --- a/resources/quality/cartesio/hips/cartesio_0.4_hips_normal.inst.cfg +++ b/resources/quality/cartesio/hips/cartesio_0.4_hips_normal.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = normal -material = generic_hips_cartesio_0.4_mm +material = generic_hips_175_cartesio_0.4_mm weight = 2 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/hips/cartesio_0.8_hips_coarse.inst.cfg b/resources/quality/cartesio/hips/cartesio_0.8_hips_coarse.inst.cfg index beff6ec6f6..7e169f54bd 100644 --- a/resources/quality/cartesio/hips/cartesio_0.8_hips_coarse.inst.cfg +++ b/resources/quality/cartesio/hips/cartesio_0.8_hips_coarse.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = coarse -material = generic_hips_cartesio_0.8_mm +material = generic_hips_175_cartesio_0.8_mm weight = 3 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/hips/cartesio_0.8_hips_extra_coarse.inst.cfg b/resources/quality/cartesio/hips/cartesio_0.8_hips_extra_coarse.inst.cfg index ea8edbbdfe..707932a7ed 100644 --- a/resources/quality/cartesio/hips/cartesio_0.8_hips_extra_coarse.inst.cfg +++ b/resources/quality/cartesio/hips/cartesio_0.8_hips_extra_coarse.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = extra coarse -material = generic_hips_cartesio_0.8_mm +material = generic_hips_175_cartesio_0.8_mm weight = 4 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/hips/cartesio_0.8_hips_high.inst.cfg b/resources/quality/cartesio/hips/cartesio_0.8_hips_high.inst.cfg index 13f139f596..3cc8f7dc25 100644 --- a/resources/quality/cartesio/hips/cartesio_0.8_hips_high.inst.cfg +++ b/resources/quality/cartesio/hips/cartesio_0.8_hips_high.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = high -material = generic_hips_cartesio_0.8_mm +material = generic_hips_175_cartesio_0.8_mm weight = 1 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/hips/cartesio_0.8_hips_normal.inst.cfg b/resources/quality/cartesio/hips/cartesio_0.8_hips_normal.inst.cfg index 06b45cd601..d9c10d815a 100644 --- a/resources/quality/cartesio/hips/cartesio_0.8_hips_normal.inst.cfg +++ b/resources/quality/cartesio/hips/cartesio_0.8_hips_normal.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = normal -material = generic_hips_cartesio_0.8_mm +material = generic_hips_175_cartesio_0.8_mm weight = 2 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/nylon/cartesio_0.25_nylon_high.inst.cfg b/resources/quality/cartesio/nylon/cartesio_0.25_nylon_high.inst.cfg index 569c5a786f..6cceba99c3 100644 --- a/resources/quality/cartesio/nylon/cartesio_0.25_nylon_high.inst.cfg +++ b/resources/quality/cartesio/nylon/cartesio_0.25_nylon_high.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = high -material = generic_nylon_cartesio_0.25_mm +material = generic_nylon_175_cartesio_0.25_mm weight = 1 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/nylon/cartesio_0.25_nylon_normal.inst.cfg b/resources/quality/cartesio/nylon/cartesio_0.25_nylon_normal.inst.cfg index 7ac13e4d60..07be2462f4 100644 --- a/resources/quality/cartesio/nylon/cartesio_0.25_nylon_normal.inst.cfg +++ b/resources/quality/cartesio/nylon/cartesio_0.25_nylon_normal.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = normal -material = generic_nylon_cartesio_0.25_mm +material = generic_nylon_175_cartesio_0.25_mm weight = 2 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/nylon/cartesio_0.4_nylon_high.inst.cfg b/resources/quality/cartesio/nylon/cartesio_0.4_nylon_high.inst.cfg index d294820c39..271278143b 100644 --- a/resources/quality/cartesio/nylon/cartesio_0.4_nylon_high.inst.cfg +++ b/resources/quality/cartesio/nylon/cartesio_0.4_nylon_high.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = high -material = generic_nylon_cartesio_0.4_mm +material = generic_nylon_175_cartesio_0.4_mm weight = 1 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/nylon/cartesio_0.4_nylon_normal.inst.cfg b/resources/quality/cartesio/nylon/cartesio_0.4_nylon_normal.inst.cfg index dd37e5f46a..32aaddf4cb 100644 --- a/resources/quality/cartesio/nylon/cartesio_0.4_nylon_normal.inst.cfg +++ b/resources/quality/cartesio/nylon/cartesio_0.4_nylon_normal.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = normal -material = generic_nylon_cartesio_0.4_mm +material = generic_nylon_175_cartesio_0.4_mm weight = 2 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/nylon/cartesio_0.8_nylon_coarse.inst.cfg b/resources/quality/cartesio/nylon/cartesio_0.8_nylon_coarse.inst.cfg index 5d303731d9..f019fec205 100644 --- a/resources/quality/cartesio/nylon/cartesio_0.8_nylon_coarse.inst.cfg +++ b/resources/quality/cartesio/nylon/cartesio_0.8_nylon_coarse.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = coarse -material = generic_nylon_cartesio_0.8_mm +material = generic_nylon_175_cartesio_0.8_mm weight = 3 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/nylon/cartesio_0.8_nylon_extra_coarse.inst.cfg b/resources/quality/cartesio/nylon/cartesio_0.8_nylon_extra_coarse.inst.cfg index 9d015d71bb..b19df96222 100644 --- a/resources/quality/cartesio/nylon/cartesio_0.8_nylon_extra_coarse.inst.cfg +++ b/resources/quality/cartesio/nylon/cartesio_0.8_nylon_extra_coarse.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = extra coarse -material = generic_nylon_cartesio_0.8_mm +material = generic_nylon_175_cartesio_0.8_mm weight = 4 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/nylon/cartesio_0.8_nylon_high.inst.cfg b/resources/quality/cartesio/nylon/cartesio_0.8_nylon_high.inst.cfg index 324149f527..adc278188d 100644 --- a/resources/quality/cartesio/nylon/cartesio_0.8_nylon_high.inst.cfg +++ b/resources/quality/cartesio/nylon/cartesio_0.8_nylon_high.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = high -material = generic_nylon_cartesio_0.8_mm +material = generic_nylon_175_cartesio_0.8_mm weight = 1 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/nylon/cartesio_0.8_nylon_normal.inst.cfg b/resources/quality/cartesio/nylon/cartesio_0.8_nylon_normal.inst.cfg index b7e9920fac..87e8515872 100644 --- a/resources/quality/cartesio/nylon/cartesio_0.8_nylon_normal.inst.cfg +++ b/resources/quality/cartesio/nylon/cartesio_0.8_nylon_normal.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = normal -material = generic_nylon_cartesio_0.8_mm +material = generic_nylon_175_cartesio_0.8_mm weight = 2 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/pc/cartesio_0.25_pc_high.inst.cfg b/resources/quality/cartesio/pc/cartesio_0.25_pc_high.inst.cfg index 224b4383f0..cb8b52cf12 100644 --- a/resources/quality/cartesio/pc/cartesio_0.25_pc_high.inst.cfg +++ b/resources/quality/cartesio/pc/cartesio_0.25_pc_high.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = high -material = generic_pc_cartesio_0.25_mm +material = generic_pc_175_cartesio_0.25_mm weight = 1 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/pc/cartesio_0.25_pc_normal.inst.cfg b/resources/quality/cartesio/pc/cartesio_0.25_pc_normal.inst.cfg index e3ab6f83d9..8cb4ebc94b 100644 --- a/resources/quality/cartesio/pc/cartesio_0.25_pc_normal.inst.cfg +++ b/resources/quality/cartesio/pc/cartesio_0.25_pc_normal.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = normal -material = generic_pc_cartesio_0.25_mm +material = generic_pc_175_cartesio_0.25_mm weight = 2 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/pc/cartesio_0.4_pc_high.inst.cfg b/resources/quality/cartesio/pc/cartesio_0.4_pc_high.inst.cfg index 213b94bfaa..3aa4211c0a 100644 --- a/resources/quality/cartesio/pc/cartesio_0.4_pc_high.inst.cfg +++ b/resources/quality/cartesio/pc/cartesio_0.4_pc_high.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = high -material = generic_pc_cartesio_0.4_mm +material = generic_pc_175_cartesio_0.4_mm weight = 1 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/pc/cartesio_0.4_pc_normal.inst.cfg b/resources/quality/cartesio/pc/cartesio_0.4_pc_normal.inst.cfg index 8c258630e1..42fc39d6f2 100644 --- a/resources/quality/cartesio/pc/cartesio_0.4_pc_normal.inst.cfg +++ b/resources/quality/cartesio/pc/cartesio_0.4_pc_normal.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = normal -material = generic_pc_cartesio_0.4_mm +material = generic_pc_175_cartesio_0.4_mm weight = 2 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/pc/cartesio_0.8_pc_coarse.inst.cfg b/resources/quality/cartesio/pc/cartesio_0.8_pc_coarse.inst.cfg index 8f29b3a679..ba2a33d4cf 100644 --- a/resources/quality/cartesio/pc/cartesio_0.8_pc_coarse.inst.cfg +++ b/resources/quality/cartesio/pc/cartesio_0.8_pc_coarse.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = coarse -material = generic_pc_cartesio_0.8_mm +material = generic_pc_175_cartesio_0.8_mm weight = 3 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/pc/cartesio_0.8_pc_extra_coarse.inst.cfg b/resources/quality/cartesio/pc/cartesio_0.8_pc_extra_coarse.inst.cfg index f8238c28b5..443f009ba4 100644 --- a/resources/quality/cartesio/pc/cartesio_0.8_pc_extra_coarse.inst.cfg +++ b/resources/quality/cartesio/pc/cartesio_0.8_pc_extra_coarse.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = extra coarse -material = generic_pc_cartesio_0.8_mm +material = generic_pc_175_cartesio_0.8_mm weight = 4 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/pc/cartesio_0.8_pc_high.inst.cfg b/resources/quality/cartesio/pc/cartesio_0.8_pc_high.inst.cfg index ca1acf1e59..9dc821ad27 100644 --- a/resources/quality/cartesio/pc/cartesio_0.8_pc_high.inst.cfg +++ b/resources/quality/cartesio/pc/cartesio_0.8_pc_high.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = high -material = generic_pc_cartesio_0.8_mm +material = generic_pc_175_cartesio_0.8_mm weight = 1 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/pc/cartesio_0.8_pc_normal.inst.cfg b/resources/quality/cartesio/pc/cartesio_0.8_pc_normal.inst.cfg index 54e2f3a8b8..893a8f0939 100644 --- a/resources/quality/cartesio/pc/cartesio_0.8_pc_normal.inst.cfg +++ b/resources/quality/cartesio/pc/cartesio_0.8_pc_normal.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = normal -material = generic_pc_cartesio_0.8_mm +material = generic_pc_175_cartesio_0.8_mm weight = 2 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/petg/cartesio_0.25_petg_high.inst.cfg b/resources/quality/cartesio/petg/cartesio_0.25_petg_high.inst.cfg index 324ff40497..a197b70f1b 100644 --- a/resources/quality/cartesio/petg/cartesio_0.25_petg_high.inst.cfg +++ b/resources/quality/cartesio/petg/cartesio_0.25_petg_high.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = high -material = generic_petg_cartesio_0.25_mm +material = generic_petg_175_cartesio_0.25_mm weight = 1 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 8 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/petg/cartesio_0.25_petg_normal.inst.cfg b/resources/quality/cartesio/petg/cartesio_0.25_petg_normal.inst.cfg index 55a04548bc..c34c7f8eae 100644 --- a/resources/quality/cartesio/petg/cartesio_0.25_petg_normal.inst.cfg +++ b/resources/quality/cartesio/petg/cartesio_0.25_petg_normal.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = normal -material = generic_petg_cartesio_0.25_mm +material = generic_petg_175_cartesio_0.25_mm weight = 2 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 8 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/petg/cartesio_0.4_petg_high.inst.cfg b/resources/quality/cartesio/petg/cartesio_0.4_petg_high.inst.cfg index c6e759c87b..8130f8563e 100644 --- a/resources/quality/cartesio/petg/cartesio_0.4_petg_high.inst.cfg +++ b/resources/quality/cartesio/petg/cartesio_0.4_petg_high.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = high -material = generic_petg_cartesio_0.4_mm +material = generic_petg_175_cartesio_0.4_mm weight = 1 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 8 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/petg/cartesio_0.4_petg_normal.inst.cfg b/resources/quality/cartesio/petg/cartesio_0.4_petg_normal.inst.cfg index 1ad1cc9f5d..84c7525b0b 100644 --- a/resources/quality/cartesio/petg/cartesio_0.4_petg_normal.inst.cfg +++ b/resources/quality/cartesio/petg/cartesio_0.4_petg_normal.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = normal -material = generic_petg_cartesio_0.4_mm +material = generic_petg_175_cartesio_0.4_mm weight = 2 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 8 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/petg/cartesio_0.8_petg_coarse.inst.cfg b/resources/quality/cartesio/petg/cartesio_0.8_petg_coarse.inst.cfg index 3df1647d57..a3a8d324fb 100644 --- a/resources/quality/cartesio/petg/cartesio_0.8_petg_coarse.inst.cfg +++ b/resources/quality/cartesio/petg/cartesio_0.8_petg_coarse.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = coarse -material = generic_petg_cartesio_0.8_mm +material = generic_petg_175_cartesio_0.8_mm weight = 3 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 8 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/petg/cartesio_0.8_petg_extra_coarse.inst.cfg b/resources/quality/cartesio/petg/cartesio_0.8_petg_extra_coarse.inst.cfg index 99a3659e18..295b3c3e2c 100644 --- a/resources/quality/cartesio/petg/cartesio_0.8_petg_extra_coarse.inst.cfg +++ b/resources/quality/cartesio/petg/cartesio_0.8_petg_extra_coarse.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = extra coarse -material = generic_petg_cartesio_0.8_mm +material = generic_petg_175_cartesio_0.8_mm weight = 4 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 8 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/petg/cartesio_0.8_petg_high.inst.cfg b/resources/quality/cartesio/petg/cartesio_0.8_petg_high.inst.cfg index 8fc6fc8398..df496e5e06 100644 --- a/resources/quality/cartesio/petg/cartesio_0.8_petg_high.inst.cfg +++ b/resources/quality/cartesio/petg/cartesio_0.8_petg_high.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = high -material = generic_petg_cartesio_0.8_mm +material = generic_petg_175_cartesio_0.8_mm weight = 1 [values] @@ -18,13 +18,12 @@ wall_0_inset = -0.05 fill_perimeter_gaps = nowhere travel_compensate_overlapping_walls_enabled = -infill_sparse_density = 40 +infill_sparse_density = 50 infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 8 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/petg/cartesio_0.8_petg_normal.inst.cfg b/resources/quality/cartesio/petg/cartesio_0.8_petg_normal.inst.cfg index 05805e41d7..fa787830b2 100644 --- a/resources/quality/cartesio/petg/cartesio_0.8_petg_normal.inst.cfg +++ b/resources/quality/cartesio/petg/cartesio_0.8_petg_normal.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = normal -material = generic_petg_cartesio_0.8_mm +material = generic_petg_175_cartesio_0.8_mm weight = 2 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 8 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/pla/cartesio_0.25_pla_high.inst.cfg b/resources/quality/cartesio/pla/cartesio_0.25_pla_high.inst.cfg index 3eac407634..633aeca2d2 100644 --- a/resources/quality/cartesio/pla/cartesio_0.25_pla_high.inst.cfg +++ b/resources/quality/cartesio/pla/cartesio_0.25_pla_high.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = high -material = generic_pla_cartesio_0.25_mm +material = generic_pla_175_cartesio_0.25_mm weight = 1 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/pla/cartesio_0.25_pla_normal.inst.cfg b/resources/quality/cartesio/pla/cartesio_0.25_pla_normal.inst.cfg index ac82dddf8a..a212b65676 100644 --- a/resources/quality/cartesio/pla/cartesio_0.25_pla_normal.inst.cfg +++ b/resources/quality/cartesio/pla/cartesio_0.25_pla_normal.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = normal -material = generic_pla_cartesio_0.25_mm +material = generic_pla_175_cartesio_0.25_mm weight = 2 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/pla/cartesio_0.4_pla_high.inst.cfg b/resources/quality/cartesio/pla/cartesio_0.4_pla_high.inst.cfg index bdb6ace957..7a7f7f31a3 100644 --- a/resources/quality/cartesio/pla/cartesio_0.4_pla_high.inst.cfg +++ b/resources/quality/cartesio/pla/cartesio_0.4_pla_high.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = high -material = generic_pla_cartesio_0.4_mm +material = generic_pla_175_cartesio_0.4_mm weight = 1 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/pla/cartesio_0.4_pla_normal.inst.cfg b/resources/quality/cartesio/pla/cartesio_0.4_pla_normal.inst.cfg index ca02ffc4a2..16ad34b9f0 100644 --- a/resources/quality/cartesio/pla/cartesio_0.4_pla_normal.inst.cfg +++ b/resources/quality/cartesio/pla/cartesio_0.4_pla_normal.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = normal -material = generic_pla_cartesio_0.4_mm +material = generic_pla_175_cartesio_0.4_mm weight = 2 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/pla/cartesio_0.8_pla_coarse.inst.cfg b/resources/quality/cartesio/pla/cartesio_0.8_pla_coarse.inst.cfg index 5a9e561177..c6a680dfcc 100644 --- a/resources/quality/cartesio/pla/cartesio_0.8_pla_coarse.inst.cfg +++ b/resources/quality/cartesio/pla/cartesio_0.8_pla_coarse.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = coarse -material = generic_pla_cartesio_0.8_mm +material = generic_pla_175_cartesio_0.8_mm weight = 3 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/pla/cartesio_0.8_pla_extra_coarse.inst.cfg b/resources/quality/cartesio/pla/cartesio_0.8_pla_extra_coarse.inst.cfg index 4ac73a3ce0..a1ea49ba92 100644 --- a/resources/quality/cartesio/pla/cartesio_0.8_pla_extra_coarse.inst.cfg +++ b/resources/quality/cartesio/pla/cartesio_0.8_pla_extra_coarse.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = extra coarse -material = generic_pla_cartesio_0.8_mm +material = generic_pla_175_cartesio_0.8_mm weight = 4 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/pla/cartesio_0.8_pla_high.inst.cfg b/resources/quality/cartesio/pla/cartesio_0.8_pla_high.inst.cfg index 582735062b..622cb124eb 100644 --- a/resources/quality/cartesio/pla/cartesio_0.8_pla_high.inst.cfg +++ b/resources/quality/cartesio/pla/cartesio_0.8_pla_high.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = high -material = generic_pla_cartesio_0.8_mm +material = generic_pla_175_cartesio_0.8_mm weight = 1 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/pla/cartesio_0.8_pla_normal.inst.cfg b/resources/quality/cartesio/pla/cartesio_0.8_pla_normal.inst.cfg index f77e2ade8c..c24f46f30d 100644 --- a/resources/quality/cartesio/pla/cartesio_0.8_pla_normal.inst.cfg +++ b/resources/quality/cartesio/pla/cartesio_0.8_pla_normal.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = normal -material = generic_pla_cartesio_0.8_mm +material = generic_pla_175_cartesio_0.8_mm weight = 2 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 10 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/pva/cartesio_0.25_pva_high.inst.cfg b/resources/quality/cartesio/pva/cartesio_0.25_pva_high.inst.cfg index 73b434365e..34b7dcf0f1 100644 --- a/resources/quality/cartesio/pva/cartesio_0.25_pva_high.inst.cfg +++ b/resources/quality/cartesio/pva/cartesio_0.25_pva_high.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = high -material = generic_pva_cartesio_0.25_mm +material = generic_pva_175_cartesio_0.25_mm weight = 1 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 8 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/pva/cartesio_0.25_pva_normal.inst.cfg b/resources/quality/cartesio/pva/cartesio_0.25_pva_normal.inst.cfg index 1415954e6c..5f6b8c846d 100644 --- a/resources/quality/cartesio/pva/cartesio_0.25_pva_normal.inst.cfg +++ b/resources/quality/cartesio/pva/cartesio_0.25_pva_normal.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = normal -material = generic_pva_cartesio_0.25_mm +material = generic_pva_175_cartesio_0.25_mm weight = 2 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 8 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/pva/cartesio_0.4_pva_high.inst.cfg b/resources/quality/cartesio/pva/cartesio_0.4_pva_high.inst.cfg index 97e48f8c7d..26e476abb0 100644 --- a/resources/quality/cartesio/pva/cartesio_0.4_pva_high.inst.cfg +++ b/resources/quality/cartesio/pva/cartesio_0.4_pva_high.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = high -material = generic_pva_cartesio_0.4_mm +material = generic_pva_175_cartesio_0.4_mm weight = 1 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 8 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/pva/cartesio_0.4_pva_normal.inst.cfg b/resources/quality/cartesio/pva/cartesio_0.4_pva_normal.inst.cfg index f0231084db..14e6c6a956 100644 --- a/resources/quality/cartesio/pva/cartesio_0.4_pva_normal.inst.cfg +++ b/resources/quality/cartesio/pva/cartesio_0.4_pva_normal.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = normal -material = generic_pva_cartesio_0.4_mm +material = generic_pva_175_cartesio_0.4_mm weight = 2 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 8 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/pva/cartesio_0.8_pva_coarse.inst.cfg b/resources/quality/cartesio/pva/cartesio_0.8_pva_coarse.inst.cfg index a9c313a7db..e009409d52 100644 --- a/resources/quality/cartesio/pva/cartesio_0.8_pva_coarse.inst.cfg +++ b/resources/quality/cartesio/pva/cartesio_0.8_pva_coarse.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = coarse -material = generic_pva_cartesio_0.8_mm +material = generic_pva_175_cartesio_0.8_mm weight = 3 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 8 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/pva/cartesio_0.8_pva_extra_coarse.inst.cfg b/resources/quality/cartesio/pva/cartesio_0.8_pva_extra_coarse.inst.cfg index 2a2e2c9d0e..bc9dc55912 100644 --- a/resources/quality/cartesio/pva/cartesio_0.8_pva_extra_coarse.inst.cfg +++ b/resources/quality/cartesio/pva/cartesio_0.8_pva_extra_coarse.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = extra coarse -material = generic_pva_cartesio_0.8_mm +material = generic_pva_175_cartesio_0.8_mm weight = 4 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 8 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/pva/cartesio_0.8_pva_high.inst.cfg b/resources/quality/cartesio/pva/cartesio_0.8_pva_high.inst.cfg index 64e8ee2902..9f5ea8a070 100644 --- a/resources/quality/cartesio/pva/cartesio_0.8_pva_high.inst.cfg +++ b/resources/quality/cartesio/pva/cartesio_0.8_pva_high.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = high -material = generic_pva_cartesio_0.8_mm +material = generic_pva_175_cartesio_0.8_mm weight = 1 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 8 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/cartesio/pva/cartesio_0.8_pva_normal.inst.cfg b/resources/quality/cartesio/pva/cartesio_0.8_pva_normal.inst.cfg index 23fa682772..275ff906d2 100644 --- a/resources/quality/cartesio/pva/cartesio_0.8_pva_normal.inst.cfg +++ b/resources/quality/cartesio/pva/cartesio_0.8_pva_normal.inst.cfg @@ -6,7 +6,7 @@ definition = cartesio [metadata] type = quality quality_type = normal -material = generic_pva_cartesio_0.8_mm +material = generic_pva_175_cartesio_0.8_mm weight = 2 [values] @@ -24,7 +24,6 @@ infill_pattern = grid material_print_temperature_layer_0 = =material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature material_final_print_temperature = =material_print_temperature -material_diameter = 1.75 retraction_min_travel = =round(line_width * 10) retraction_prime_speed = 8 switch_extruder_retraction_amount = 2 diff --git a/resources/quality/ultimaker3/um3_aa0.4_ABS_Draft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_ABS_Draft_Print.inst.cfg index 00d93f3575..f0f07f7973 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_ABS_Draft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_ABS_Draft_Print.inst.cfg @@ -15,7 +15,7 @@ machine_nozzle_heat_up_speed = 1.5 material_print_temperature = =default_material_print_temperature + 10 material_initial_print_temperature = =material_print_temperature - 5 material_final_print_temperature = =material_print_temperature - 10 -prime_tower_size = 16 +prime_tower_enable = False skin_overlap = 20 speed_print = 60 speed_layer_0 = 20 diff --git a/resources/quality/ultimaker3/um3_aa0.4_ABS_Fast_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_ABS_Fast_Print.inst.cfg index 066a044ee0..0b062b0cc0 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_ABS_Fast_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_ABS_Fast_Print.inst.cfg @@ -17,7 +17,7 @@ material_print_temperature = =default_material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature - 5 material_final_print_temperature = =material_print_temperature - 10 material_standby_temperature = 100 -prime_tower_size = 16 +prime_tower_enable = False speed_print = 60 speed_layer_0 = 20 speed_topbottom = =math.ceil(speed_print * 30 / 60) diff --git a/resources/quality/ultimaker3/um3_aa0.4_ABS_High_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_ABS_High_Quality.inst.cfg index 850af33c27..26ca695cf8 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_ABS_High_Quality.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_ABS_High_Quality.inst.cfg @@ -17,7 +17,7 @@ material_standby_temperature = 100 material_print_temperature = =default_material_print_temperature - 5 material_initial_print_temperature = =material_print_temperature - 5 material_final_print_temperature = =material_print_temperature - 10 -prime_tower_size = 16 +prime_tower_enable = False speed_print = 50 speed_layer_0 = 20 speed_topbottom = =math.ceil(speed_print * 30 / 50) diff --git a/resources/quality/ultimaker3/um3_aa0.4_ABS_Normal_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_ABS_Normal_Quality.inst.cfg index 3793bf8b5e..c3f3fb83b9 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_ABS_Normal_Quality.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_ABS_Normal_Quality.inst.cfg @@ -15,7 +15,7 @@ machine_nozzle_heat_up_speed = 1.5 material_initial_print_temperature = =material_print_temperature - 5 material_final_print_temperature = =material_print_temperature - 10 material_standby_temperature = 100 -prime_tower_size = 16 +prime_tower_enable = False speed_print = 55 speed_layer_0 = 20 speed_topbottom = =math.ceil(speed_print * 30 / 55) diff --git a/resources/quality/ultimaker3/um3_aa0.4_CPEP_Draft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_CPEP_Draft_Print.inst.cfg index ee03b6dbcf..8f37595632 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_CPEP_Draft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_CPEP_Draft_Print.inst.cfg @@ -29,7 +29,6 @@ material_print_temperature_layer_0 = =material_print_temperature material_standby_temperature = 100 multiple_mesh_overlap = 0 prime_tower_enable = True -prime_tower_size = 17 prime_tower_wipe_enabled = True retraction_combing = off retraction_extrusion_window = 1 diff --git a/resources/quality/ultimaker3/um3_aa0.4_CPEP_Fast_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_CPEP_Fast_Print.inst.cfg index b61a7ee9de..3ae04bba2f 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_CPEP_Fast_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_CPEP_Fast_Print.inst.cfg @@ -29,7 +29,6 @@ material_print_temperature_layer_0 = =material_print_temperature material_standby_temperature = 100 multiple_mesh_overlap = 0 prime_tower_enable = True -prime_tower_size = 17 prime_tower_wipe_enabled = True retraction_combing = off retraction_extrusion_window = 1 diff --git a/resources/quality/ultimaker3/um3_aa0.4_CPEP_High_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_CPEP_High_Quality.inst.cfg index 1507de5a6b..c32eb2cf54 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_CPEP_High_Quality.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_CPEP_High_Quality.inst.cfg @@ -31,7 +31,6 @@ material_print_temperature_layer_0 = =material_print_temperature material_standby_temperature = 100 multiple_mesh_overlap = 0 prime_tower_enable = True -prime_tower_size = 17 prime_tower_wipe_enabled = True retraction_combing = off retraction_extrusion_window = 1 diff --git a/resources/quality/ultimaker3/um3_aa0.4_CPEP_Normal_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_CPEP_Normal_Quality.inst.cfg index 88090b12cd..515d6e6424 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_CPEP_Normal_Quality.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_CPEP_Normal_Quality.inst.cfg @@ -30,7 +30,6 @@ material_print_temperature_layer_0 = =material_print_temperature material_standby_temperature = 100 multiple_mesh_overlap = 0 prime_tower_enable = True -prime_tower_size = 17 prime_tower_wipe_enabled = True retraction_combing = off retraction_extrusion_window = 1 diff --git a/resources/quality/ultimaker3/um3_aa0.4_CPE_Draft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_CPE_Draft_Print.inst.cfg index 7a536ce033..1df6d2dc6c 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_CPE_Draft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_CPE_Draft_Print.inst.cfg @@ -14,7 +14,6 @@ material_print_temperature = =default_material_print_temperature + 10 material_initial_print_temperature = =material_print_temperature - 5 material_final_print_temperature = =material_print_temperature - 10 material_standby_temperature = 100 -prime_tower_size = 17 skin_overlap = 20 speed_print = 60 speed_layer_0 = 20 diff --git a/resources/quality/ultimaker3/um3_aa0.4_CPE_Fast_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_CPE_Fast_Print.inst.cfg index 96467fe36c..6216c1140f 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_CPE_Fast_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_CPE_Fast_Print.inst.cfg @@ -15,7 +15,6 @@ material_print_temperature = =default_material_print_temperature + 5 material_initial_print_temperature = =material_print_temperature - 5 material_final_print_temperature = =material_print_temperature - 10 material_standby_temperature = 100 -prime_tower_size = 17 speed_print = 60 speed_layer_0 = 20 speed_topbottom = =math.ceil(speed_print * 30 / 60) diff --git a/resources/quality/ultimaker3/um3_aa0.4_CPE_High_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_CPE_High_Quality.inst.cfg index 1fd6167e67..390d3466bb 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_CPE_High_Quality.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_CPE_High_Quality.inst.cfg @@ -17,7 +17,6 @@ material_print_temperature = =default_material_print_temperature - 5 material_initial_print_temperature = =material_print_temperature - 5 material_final_print_temperature = =material_print_temperature - 10 material_standby_temperature = 100 -prime_tower_size = 17 speed_print = 50 speed_layer_0 = 20 speed_topbottom = =math.ceil(speed_print * 30 / 50) diff --git a/resources/quality/ultimaker3/um3_aa0.4_CPE_Normal_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_CPE_Normal_Quality.inst.cfg index 5ad1ef6b43..f0f5228387 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_CPE_Normal_Quality.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_CPE_Normal_Quality.inst.cfg @@ -15,7 +15,6 @@ machine_nozzle_heat_up_speed = 1.5 material_initial_print_temperature = =material_print_temperature - 5 material_final_print_temperature = =material_print_temperature - 10 material_standby_temperature = 100 -prime_tower_size = 17 speed_print = 55 speed_layer_0 = 20 speed_topbottom = =math.ceil(speed_print * 30 / 55) diff --git a/resources/quality/ultimaker3/um3_aa0.4_PC_Draft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_PC_Draft_Print.inst.cfg index 94b40e427c..a601ec9531 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_PC_Draft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_PC_Draft_Print.inst.cfg @@ -36,7 +36,6 @@ material_standby_temperature = 100 multiple_mesh_overlap = 0 ooze_shield_angle = 40 prime_tower_enable = True -prime_tower_size = 16 prime_tower_wipe_enabled = True raft_airgap = 0.25 retraction_count_max = 80 diff --git a/resources/quality/ultimaker3/um3_aa0.4_PC_Fast_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_PC_Fast_Print.inst.cfg index 5d848d67dc..8fbaea6da9 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_PC_Fast_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_PC_Fast_Print.inst.cfg @@ -35,7 +35,6 @@ material_standby_temperature = 100 multiple_mesh_overlap = 0 ooze_shield_angle = 40 prime_tower_enable = True -prime_tower_size = 16 prime_tower_wipe_enabled = True raft_airgap = 0.25 retraction_count_max = 80 diff --git a/resources/quality/ultimaker3/um3_aa0.4_PC_High_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_PC_High_Quality.inst.cfg index 451aa19f60..4e0baac3d4 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_PC_High_Quality.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_PC_High_Quality.inst.cfg @@ -36,7 +36,6 @@ material_standby_temperature = 100 multiple_mesh_overlap = 0 ooze_shield_angle = 40 prime_tower_enable = True -prime_tower_size = 16 prime_tower_wipe_enabled = True raft_airgap = 0.25 retraction_count_max = 80 diff --git a/resources/quality/ultimaker3/um3_aa0.4_PC_Normal_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_PC_Normal_Quality.inst.cfg index cc50189e8c..8ee6cbd2d0 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_PC_Normal_Quality.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_PC_Normal_Quality.inst.cfg @@ -33,7 +33,6 @@ material_standby_temperature = 100 multiple_mesh_overlap = 0 ooze_shield_angle = 40 prime_tower_enable = True -prime_tower_size = 16 prime_tower_wipe_enabled = True raft_airgap = 0.25 retraction_count_max = 80 diff --git a/resources/quality/ultimaker3/um3_aa0.4_TPU_Draft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_TPU_Draft_Print.inst.cfg index 1787b266e2..14ec88fe6b 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_TPU_Draft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_TPU_Draft_Print.inst.cfg @@ -38,7 +38,6 @@ material_print_temperature_layer_0 = =default_material_print_temperature + 2 material_standby_temperature = 100 multiple_mesh_overlap = 0 prime_tower_enable = True -prime_tower_size = 16 prime_tower_wipe_enabled = True retraction_count_max = 12 retraction_extra_prime_amount = 0.8 diff --git a/resources/quality/ultimaker3/um3_aa0.4_TPU_Fast_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_TPU_Fast_Print.inst.cfg index f53d3fd285..4d14349ab0 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_TPU_Fast_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_TPU_Fast_Print.inst.cfg @@ -38,7 +38,6 @@ material_print_temperature_layer_0 = =default_material_print_temperature + 2 material_standby_temperature = 100 multiple_mesh_overlap = 0 prime_tower_enable = True -prime_tower_size = 16 prime_tower_wipe_enabled = True retraction_amount = 7 retraction_count_max = 12 diff --git a/resources/quality/ultimaker3/um3_aa0.4_TPU_Normal_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.4_TPU_Normal_Quality.inst.cfg index 0b475eda92..98d6b42cb7 100644 --- a/resources/quality/ultimaker3/um3_aa0.4_TPU_Normal_Quality.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.4_TPU_Normal_Quality.inst.cfg @@ -36,7 +36,6 @@ material_print_temperature_layer_0 = =default_material_print_temperature material_standby_temperature = 100 multiple_mesh_overlap = 0 prime_tower_enable = True -prime_tower_size = 16 prime_tower_wipe_enabled = True retraction_count_max = 12 retraction_extra_prime_amount = 0.8 diff --git a/resources/quality/ultimaker3/um3_aa0.8_CPE_Draft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_CPE_Draft_Print.inst.cfg index dbee576a94..ec1a384804 100644 --- a/resources/quality/ultimaker3/um3_aa0.8_CPE_Draft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.8_CPE_Draft_Print.inst.cfg @@ -14,6 +14,7 @@ brim_width = 15 line_width = =machine_nozzle_size * 0.875 material_print_temperature = =default_material_print_temperature + 15 material_standby_temperature = 100 +prime_tower_enable = True speed_print = 40 speed_topbottom = =math.ceil(speed_print * 25 / 40) speed_wall = =math.ceil(speed_print * 30 / 40) diff --git a/resources/quality/ultimaker3/um3_aa0.8_CPE_Superdraft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_CPE_Superdraft_Print.inst.cfg index 9aa8b69381..372b5a41c1 100644 --- a/resources/quality/ultimaker3/um3_aa0.8_CPE_Superdraft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.8_CPE_Superdraft_Print.inst.cfg @@ -15,6 +15,7 @@ layer_height = 0.4 line_width = =machine_nozzle_size * 0.875 material_print_temperature = =default_material_print_temperature + 20 material_standby_temperature = 100 +prime_tower_enable = True speed_print = 45 speed_topbottom = =math.ceil(speed_print * 30 / 45) speed_wall = =math.ceil(speed_print * 40 / 45) diff --git a/resources/quality/ultimaker3/um3_aa0.8_CPE_Verydraft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_CPE_Verydraft_Print.inst.cfg index 3f897c91d3..ec2f024141 100644 --- a/resources/quality/ultimaker3/um3_aa0.8_CPE_Verydraft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.8_CPE_Verydraft_Print.inst.cfg @@ -15,6 +15,7 @@ layer_height = 0.3 line_width = =machine_nozzle_size * 0.875 material_print_temperature = =default_material_print_temperature + 17 material_standby_temperature = 100 +prime_tower_enable = True speed_print = 40 speed_topbottom = =math.ceil(speed_print * 25 / 40) speed_wall = =math.ceil(speed_print * 30 / 40) diff --git a/resources/quality/ultimaker3/um3_aa0.8_Nylon_Draft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_Nylon_Draft_Print.inst.cfg index 30d9dccb19..c425cd3e56 100644 --- a/resources/quality/ultimaker3/um3_aa0.8_Nylon_Draft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.8_Nylon_Draft_Print.inst.cfg @@ -19,7 +19,6 @@ machine_nozzle_cool_down_speed = 0.9 machine_nozzle_heat_up_speed = 1.4 material_standby_temperature = 100 ooze_shield_angle = 40 -prime_tower_size = 15 raft_acceleration = =acceleration_layer_0 raft_airgap = =round(layer_height_0 * 0.85, 2) raft_interface_thickness = =round(machine_nozzle_size * 0.3 / 0.4, 2) diff --git a/resources/quality/ultimaker3/um3_aa0.8_Nylon_Superdraft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_Nylon_Superdraft_Print.inst.cfg index b2348c7a30..071606b8b8 100644 --- a/resources/quality/ultimaker3/um3_aa0.8_Nylon_Superdraft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.8_Nylon_Superdraft_Print.inst.cfg @@ -20,7 +20,6 @@ machine_nozzle_cool_down_speed = 0.9 machine_nozzle_heat_up_speed = 1.4 material_standby_temperature = 100 ooze_shield_angle = 40 -prime_tower_size = 15 raft_acceleration = =acceleration_layer_0 raft_airgap = =round(layer_height_0 * 0.85, 2) raft_interface_thickness = =round(machine_nozzle_size * 0.3 / 0.4, 2) diff --git a/resources/quality/ultimaker3/um3_aa0.8_Nylon_Verydraft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_Nylon_Verydraft_Print.inst.cfg index 42b09bd272..fe023b9e32 100644 --- a/resources/quality/ultimaker3/um3_aa0.8_Nylon_Verydraft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.8_Nylon_Verydraft_Print.inst.cfg @@ -20,7 +20,6 @@ machine_nozzle_cool_down_speed = 0.9 machine_nozzle_heat_up_speed = 1.4 material_standby_temperature = 100 ooze_shield_angle = 40 -prime_tower_size = 15 raft_acceleration = =acceleration_layer_0 raft_airgap = =round(layer_height_0 * 0.85, 2) raft_interface_thickness = =round(machine_nozzle_size * 0.3 / 0.4, 2) diff --git a/resources/quality/ultimaker3/um3_aa0.8_PLA_Draft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_PLA_Draft_Print.inst.cfg index b9222d6350..75ebfb6a45 100644 --- a/resources/quality/ultimaker3/um3_aa0.8_PLA_Draft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.8_PLA_Draft_Print.inst.cfg @@ -25,7 +25,6 @@ material_final_print_temperature = =max(-273.15, material_print_temperature - 15 material_initial_print_temperature = =max(-273.15, material_print_temperature - 10) material_print_temperature = =default_material_print_temperature + 10 material_standby_temperature = 100 -prime_tower_size = 15 support_angle = 70 support_line_width = =line_width * 0.75 support_pattern = ='triangles' diff --git a/resources/quality/ultimaker3/um3_aa0.8_PLA_Superdraft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_PLA_Superdraft_Print.inst.cfg index e9f081ef4a..8b46d9d22a 100644 --- a/resources/quality/ultimaker3/um3_aa0.8_PLA_Superdraft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.8_PLA_Superdraft_Print.inst.cfg @@ -26,7 +26,6 @@ material_final_print_temperature = =max(-273.15, material_print_temperature - 15 material_initial_print_temperature = =max(-273.15, material_print_temperature - 10) material_print_temperature = =default_material_print_temperature + 15 material_standby_temperature = 100 -prime_tower_size = 15 raft_margin = 10 support_angle = 70 support_line_width = =line_width * 0.75 diff --git a/resources/quality/ultimaker3/um3_aa0.8_PLA_Verydraft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_PLA_Verydraft_Print.inst.cfg index af18a87a20..a8fc688b56 100644 --- a/resources/quality/ultimaker3/um3_aa0.8_PLA_Verydraft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.8_PLA_Verydraft_Print.inst.cfg @@ -26,7 +26,6 @@ material_final_print_temperature = =max(-273.15, material_print_temperature - 15 material_initial_print_temperature = =max(-273.15, material_print_temperature - 10) material_print_temperature = =default_material_print_temperature + 10 material_standby_temperature = 100 -prime_tower_size = 15 support_angle = 70 support_line_width = =line_width * 0.75 support_pattern = ='triangles' diff --git a/resources/quality/ultimaker3/um3_aa0.8_TPU_Normal_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_TPU_Draft_Print.inst.cfg similarity index 100% rename from resources/quality/ultimaker3/um3_aa0.8_TPU_Normal_Print.inst.cfg rename to resources/quality/ultimaker3/um3_aa0.8_TPU_Draft_Print.inst.cfg diff --git a/resources/themes/cura/icons/gradual.svg b/resources/themes/cura/icons/gradual.svg new file mode 100644 index 0000000000..ed7f301e18 --- /dev/null +++ b/resources/themes/cura/icons/gradual.svg @@ -0,0 +1,102 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/resources/themes/cura/icons/material_not_selected.svg b/resources/themes/cura/icons/material_not_selected.svg new file mode 100644 index 0000000000..9b3cad88bd --- /dev/null +++ b/resources/themes/cura/icons/material_not_selected.svg @@ -0,0 +1,77 @@ + + + + + + image/svg+xml + + Artboard 3 + + + + + + Artboard 3 + Created with Sketch. + + + + + + + + + + diff --git a/resources/themes/cura/icons/material_selected.svg b/resources/themes/cura/icons/material_selected.svg new file mode 100644 index 0000000000..6589eac416 --- /dev/null +++ b/resources/themes/cura/icons/material_selected.svg @@ -0,0 +1,78 @@ + + + + + + image/svg+xml + + Artboard 3 Copy + + + + + + Artboard 3 Copy + Created with Sketch. + + + + + + + + + + diff --git a/resources/themes/cura/styles.qml b/resources/themes/cura/styles.qml index a7c7dcb6cd..ffe866f2c6 100755 --- a/resources/themes/cura/styles.qml +++ b/resources/themes/cura/styles.qml @@ -8,19 +8,25 @@ import QtQuick.Controls.Styles 1.1 import UM 1.1 as UM QtObject { - property Component toggle_button: Component { + property Component mode_switch: Component { SwitchStyle { groove: Rectangle { - implicitWidth: UM.Theme.getSize("toggle_button_background_implicit_size").width - implicitHeight: UM.Theme.getSize("toggle_button_background_implicit_size").height - radius: UM.Theme.getSize("toggle_button_radius").width - border.color: { - if (control.pressed || (control.checkable && control.checked)) { - return UM.Theme.getColor("sidebar_header_active"); - } else if(control.hovered) { - return UM.Theme.getColor("sidebar_header_hover"); + implicitWidth: UM.Theme.getSize("mode_switch").width + implicitHeight: UM.Theme.getSize("mode_switch").height + radius: implicitHeight / 2 + color: { + if(control.hovered || control._hovered) { + return UM.Theme.getColor("mode_switch_hover"); } else { - return UM.Theme.getColor("sidebar_header_bar"); + return UM.Theme.getColor("mode_switch"); + } + } + Behavior on color { ColorAnimation { duration: 50; } } + border.color: { + if(control.hovered || control._hovered) { + return UM.Theme.getColor("mode_switch_border_hover"); + } else { + return UM.Theme.getColor("mode_switch_border"); } } Behavior on border.color { ColorAnimation { duration: 50; } } @@ -28,9 +34,9 @@ QtObject { } handle: Rectangle { - implicitWidth: UM.Theme.getSize("toggle_button_knob_implicit_size").width - implicitHeight: UM.Theme.getSize("toggle_button_knob_implicit_size").height - radius: UM.Theme.getSize("toggle_button_radius").width + implicitWidth: implicitHeight + implicitHeight: UM.Theme.getSize("mode_switch").height + radius: implicitHeight / 2 color: { if (control.pressed || (control.checkable && control.checked)) { @@ -206,7 +212,9 @@ QtObject { property bool down: control.pressed || (control.checkable && control.checked); color: { - if(control.checkable && control.checked && control.hovered) { + if(control.customColor !== undefined && control.customColor !== null) { + return control.customColor + } else if(control.checkable && control.checked && control.hovered) { return Theme.getColor("button_active_hover"); } else if(control.pressed || (control.checkable && control.checked)) { return Theme.getColor("button_active"); diff --git a/resources/themes/cura/theme.json b/resources/themes/cura/theme.json index 084ee27bb2..5f0b3656c8 100644 --- a/resources/themes/cura/theme.json +++ b/resources/themes/cura/theme.json @@ -175,6 +175,15 @@ "checkbox_mark": [24, 41, 77, 255], "checkbox_text": [24, 41, 77, 255], + "mode_switch": [255, 255, 255, 255], + "mode_switch_hover": [255, 255, 255, 255], + "mode_switch_border": [127, 127, 127, 255], + "mode_switch_border_hover": [12, 169, 227, 255], + "mode_switch_handle": [24, 41, 77, 255], + "mode_switch_text": [24, 41, 77, 255], + "mode_switch_text_hover": [24, 41, 77, 255], + "mode_switch_text_checked": [12, 169, 227, 255], + "tooltip": [12, 169, 227, 255], "tooltip_text": [255, 255, 255, 255], @@ -238,7 +247,7 @@ }, "sizes": { - "window_minimum_size": [70, 54], + "window_minimum_size": [70, 50], "window_margin": [1.0, 1.0], "default_margin": [1.0, 1.0], "default_lining": [0.08, 0.08], @@ -301,6 +310,7 @@ "layerview_row_spacing": [0.0, 0.5], "checkbox": [2.0, 2.0], + "mode_switch": [2.0, 1.0], "tooltip": [20.0, 10.0], "tooltip_margins": [1.0, 1.0], @@ -319,11 +329,6 @@ "infill_button_margin": [0.5, 0.5], - "jobspecs_line": [2.0, 2.0], - - "toggle_button_text_anchoring_margin": [1.0, 1.0], - "toggle_button_radius": [1.0, 1.0], - "toggle_button_background_implicit_size": [2.0, 1.0], - "toggle_button_knob_implicit_size": [1.0, 1.0] + "jobspecs_line": [2.0, 2.0] } } diff --git a/resources/variants/cartesio_0.8.inst.cfg b/resources/variants/cartesio_0.8.inst.cfg index d84a45e615..0f6fda119a 100644 --- a/resources/variants/cartesio_0.8.inst.cfg +++ b/resources/variants/cartesio_0.8.inst.cfg @@ -10,3 +10,5 @@ type = variant [values] machine_nozzle_size = 0.8 machine_nozzle_tip_outer_diameter = 1.05 + +prime_tower_line_width = 0.69 diff --git a/resources/variants/ultimaker2_0.25.inst.cfg b/resources/variants/ultimaker2_0.25.inst.cfg new file mode 100644 index 0000000000..9d59e47b89 --- /dev/null +++ b/resources/variants/ultimaker2_0.25.inst.cfg @@ -0,0 +1,12 @@ +[general] +name = 0.25 mm +version = 2 +definition = ultimaker2 + +[metadata] +author = Ultimaker +type = variant + +[values] +machine_nozzle_size = 0.25 +machine_nozzle_tip_outer_diameter = 0.8 diff --git a/resources/variants/ultimaker2_0.4.inst.cfg b/resources/variants/ultimaker2_0.4.inst.cfg new file mode 100644 index 0000000000..7ebcbb69b7 --- /dev/null +++ b/resources/variants/ultimaker2_0.4.inst.cfg @@ -0,0 +1,12 @@ +[general] +name = 0.4 mm +version = 2 +definition = ultimaker2 + +[metadata] +author = Ultimaker +type = variant + +[values] +machine_nozzle_size = 0.4 +machine_nozzle_tip_outer_diameter = 1.05 diff --git a/resources/variants/ultimaker2_0.6.inst.cfg b/resources/variants/ultimaker2_0.6.inst.cfg new file mode 100644 index 0000000000..accf507750 --- /dev/null +++ b/resources/variants/ultimaker2_0.6.inst.cfg @@ -0,0 +1,12 @@ +[general] +name = 0.6 mm +version = 2 +definition = ultimaker2 + +[metadata] +author = Ultimaker +type = variant + +[values] +machine_nozzle_size = 0.6 +machine_nozzle_tip_outer_diameter = 1.25 diff --git a/resources/variants/ultimaker2_0.8.inst.cfg b/resources/variants/ultimaker2_0.8.inst.cfg new file mode 100644 index 0000000000..97452046cb --- /dev/null +++ b/resources/variants/ultimaker2_0.8.inst.cfg @@ -0,0 +1,12 @@ +[general] +name = 0.8 mm +version = 2 +definition = ultimaker2 + +[metadata] +author = Ultimaker +type = variant + +[values] +machine_nozzle_size = 0.8 +machine_nozzle_tip_outer_diameter = 1.35 diff --git a/resources/variants/ultimaker3_aa0.8.inst.cfg b/resources/variants/ultimaker3_aa0.8.inst.cfg index e7e1654c6e..925346c66d 100644 --- a/resources/variants/ultimaker3_aa0.8.inst.cfg +++ b/resources/variants/ultimaker3_aa0.8.inst.cfg @@ -37,7 +37,6 @@ material_initial_print_temperature = =material_print_temperature - 5 material_standby_temperature = 100 multiple_mesh_overlap = 0 prime_tower_enable = False -prime_tower_size = 16 prime_tower_wipe_enabled = True retract_at_layer_change = True retraction_amount = 6.5 diff --git a/resources/variants/ultimaker3_extended_aa0.8.inst.cfg b/resources/variants/ultimaker3_extended_aa0.8.inst.cfg index b89ce4406b..9dec070e20 100644 --- a/resources/variants/ultimaker3_extended_aa0.8.inst.cfg +++ b/resources/variants/ultimaker3_extended_aa0.8.inst.cfg @@ -37,7 +37,6 @@ material_initial_print_temperature = =material_print_temperature - 5 material_standby_temperature = 100 multiple_mesh_overlap = 0 prime_tower_enable = False -prime_tower_size = 16 prime_tower_wipe_enabled = True retract_at_layer_change = True retraction_amount = 6.5 diff --git a/tests/Settings/TestCuraContainerRegistry.py b/tests/Settings/TestCuraContainerRegistry.py new file mode 100644 index 0000000000..7b191a8376 --- /dev/null +++ b/tests/Settings/TestCuraContainerRegistry.py @@ -0,0 +1,99 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + +import os #To find the directory with test files and find the test files. +import pytest #This module contains unit tests. +import shutil #To copy files to make a temporary file. +import unittest.mock #To mock and monkeypatch stuff. +import urllib.parse + +from cura.Settings.CuraContainerRegistry import CuraContainerRegistry #The class we're testing. +from cura.Settings.ExtruderStack import ExtruderStack #Testing for returning the correct types of stacks. +from cura.Settings.GlobalStack import GlobalStack #Testing for returning the correct types of stacks. +from UM.Resources import Resources #Mocking some functions of this. +import UM.Settings.ContainerRegistry #Making empty container stacks. +import UM.Settings.ContainerStack #Setting the container registry here properly. +from UM.Settings.DefinitionContainer import DefinitionContainer + +## Gives a fresh CuraContainerRegistry instance. +@pytest.fixture() +def container_registry(): + return CuraContainerRegistry() + +def teardown(): + #If the temporary file for the legacy file rename test still exists, remove it. + temporary_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), "stacks", "temporary.stack.cfg") + if os.path.isfile(temporary_file): + os.remove(temporary_file) + +## Tests whether loading gives objects of the correct type. +@pytest.mark.parametrize("filename, output_class", [ + ("ExtruderLegacy.stack.cfg", ExtruderStack), + ("MachineLegacy.stack.cfg", GlobalStack), + ("Left.extruder.cfg", ExtruderStack), + ("Global.global.cfg", GlobalStack), + ("Global.stack.cfg", GlobalStack) +]) +def test_loadTypes(filename, output_class, container_registry): + #Mock some dependencies. + UM.Settings.ContainerStack.setContainerRegistry(container_registry) + Resources.getAllResourcesOfType = unittest.mock.MagicMock(return_value = [os.path.join(os.path.dirname(os.path.abspath(__file__)), "stacks", filename)]) #Return just this tested file. + + def findContainers(container_type = 0, id = None): + if id == "some_instance": + return [UM.Settings.ContainerRegistry._EmptyInstanceContainer(id)] + elif id == "some_definition": + return [DefinitionContainer(container_id = id)] + else: + return [] + + container_registry.findContainers = findContainers + + with unittest.mock.patch("cura.Settings.GlobalStack.GlobalStack.findContainer"): + with unittest.mock.patch("os.remove"): + container_registry.load() + + #Check whether the resulting type was correct. + stack_id = filename.split(".")[0] + for container in container_registry._containers: #Stupid ContainerRegistry class doesn't expose any way of getting at this except by prodding the privates. + if container.getId() == stack_id: #This is the one we're testing. + assert type(container) == output_class + break + else: + assert False #Container stack with specified ID was not loaded. + +## Tests whether loading a legacy file moves the upgraded file properly. +def test_loadLegacyFileRenamed(container_registry): + #Create a temporary file for the registry to load. + stacks_folder = os.path.join(os.path.dirname(os.path.abspath(__file__)), "stacks") + temp_file = os.path.join(stacks_folder, "temporary.stack.cfg") + temp_file_source = os.path.join(stacks_folder, "MachineLegacy.stack.cfg") + shutil.copyfile(temp_file_source, temp_file) + + #Mock some dependencies. + UM.Settings.ContainerStack.setContainerRegistry(container_registry) + Resources.getAllResourcesOfType = unittest.mock.MagicMock(return_value = [temp_file]) #Return a temporary file that we'll make for this test. + + def findContainers(container_type = 0, id = None): + if id == "MachineLegacy": + return None + + container = UM.Settings.ContainerRegistry._EmptyInstanceContainer(id) + container.getNextStack = unittest.mock.MagicMock() + return [container] + + old_find_containers = container_registry.findContainers + container_registry.findContainers = findContainers + + with unittest.mock.patch("cura.Settings.GlobalStack.GlobalStack.findContainer"): + container_registry.load() + + container_registry.findContainers = old_find_containers + + container_registry.saveAll() + print("all containers in registry", container_registry._containers) + assert not os.path.isfile(temp_file) + mime_type = container_registry.getMimeTypeForContainer(GlobalStack) + file_name = urllib.parse.quote_plus("MachineLegacy") + "." + mime_type.preferredSuffix + path = Resources.getStoragePath(Resources.ContainerStacks, file_name) + assert os.path.isfile(path) diff --git a/tests/Settings/TestExtruderStack.py b/tests/Settings/TestExtruderStack.py new file mode 100644 index 0000000000..4e55411d9d --- /dev/null +++ b/tests/Settings/TestExtruderStack.py @@ -0,0 +1,391 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + +import pytest #This module contains automated tests. +import unittest.mock #For the mocking and monkeypatching functionality. + +import UM.Settings.ContainerRegistry #To create empty instance containers. +import UM.Settings.ContainerStack #To set the container registry the container stacks use. +from UM.Settings.DefinitionContainer import DefinitionContainer #To check against the class of DefinitionContainer. +from UM.Settings.InstanceContainer import InstanceContainer #To check against the class of InstanceContainer. +import cura.Settings.ExtruderStack #The module we're testing. +from cura.Settings.Exceptions import InvalidContainerError, InvalidOperationError #To check whether the correct exceptions are raised. + +from cura.Settings.ExtruderManager import ExtruderManager + +## Fake container registry that always provides all containers you ask of. +@pytest.yield_fixture() +def container_registry(): + registry = unittest.mock.MagicMock() + registry.return_value = unittest.mock.NonCallableMagicMock() + registry.findInstanceContainers = lambda *args, registry = registry, **kwargs: [registry.return_value] + registry.findDefinitionContainers = lambda *args, registry = registry, **kwargs: [registry.return_value] + + UM.Settings.ContainerRegistry.ContainerRegistry._ContainerRegistry__instance = registry + UM.Settings.ContainerStack._containerRegistry = registry + + yield registry + + UM.Settings.ContainerRegistry.ContainerRegistry._ContainerRegistry__instance = None + UM.Settings.ContainerStack._containerRegistry = None + +## An empty extruder stack to test with. +@pytest.fixture() +def extruder_stack() -> cura.Settings.ExtruderStack.ExtruderStack: + return cura.Settings.ExtruderStack.ExtruderStack("TestStack") + +## Gets an instance container with a specified container type. +# +# \param container_type The type metadata for the instance container. +# \return An instance container instance. +def getInstanceContainer(container_type) -> InstanceContainer: + container = InstanceContainer(container_id = "InstanceContainer") + container.addMetaDataEntry("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.addMetaDataEntry("type", container_type) + +#############################START OF TEST CASES################################ + +## Tests whether adding a container is properly forbidden. +def test_addContainer(extruder_stack): + with pytest.raises(InvalidOperationError): + extruder_stack.addContainer(unittest.mock.MagicMock()) + +#Tests setting user changes profiles to invalid containers. +@pytest.mark.parametrize("container", [ + getInstanceContainer(container_type = "wrong container type"), + getInstanceContainer(container_type = "material"), #Existing, but still wrong type. + DefinitionContainer(container_id = "wrong class") +]) +def test_constrainUserChangesInvalid(container, extruder_stack): + with pytest.raises(InvalidContainerError): #Invalid container, should raise an error. + extruder_stack.userChanges = container + +#Tests setting user changes profiles. +@pytest.mark.parametrize("container", [ + getInstanceContainer(container_type = "user"), + InstanceContainerSubClass(container_type = "user") +]) +def test_constrainUserChangesValid(container, extruder_stack): + extruder_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"), + getInstanceContainer(container_type = "material"), #Existing, but still wrong type. + DefinitionContainer(container_id = "wrong class") +]) +def test_constrainQualityChangesInvalid(container, extruder_stack): + with pytest.raises(InvalidContainerError): #Invalid container, should raise an error. + extruder_stack.qualityChanges = container + +#Test setting quality changes profiles. +@pytest.mark.parametrize("container", [ + getInstanceContainer(container_type = "quality_changes"), + InstanceContainerSubClass(container_type = "quality_changes") +]) +def test_constrainQualityChangesValid(container, extruder_stack): + extruder_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"), + getInstanceContainer(container_type = "material"), #Existing, but still wrong type. + DefinitionContainer(container_id = "wrong class") +]) +def test_constrainQualityInvalid(container, extruder_stack): + with pytest.raises(InvalidContainerError): #Invalid container, should raise an error. + extruder_stack.quality = container + +#Test setting quality profiles. +@pytest.mark.parametrize("container", [ + getInstanceContainer(container_type = "quality"), + InstanceContainerSubClass(container_type = "quality") +]) +def test_constrainQualityValid(container, extruder_stack): + extruder_stack.quality = container #Should not give an error. + +#Tests setting materials to invalid containers. +@pytest.mark.parametrize("container", [ + getInstanceContainer(container_type = "wrong container type"), + getInstanceContainer(container_type = "quality"), #Existing, but still wrong type. + DefinitionContainer(container_id = "wrong class") +]) +def test_constrainMaterialInvalid(container, extruder_stack): + with pytest.raises(InvalidContainerError): #Invalid container, should raise an error. + extruder_stack.material = container + +#Test setting materials. +@pytest.mark.parametrize("container", [ + getInstanceContainer(container_type = "material"), + InstanceContainerSubClass(container_type = "material") +]) +def test_constrainMaterialValid(container, extruder_stack): + extruder_stack.material = container #Should not give an error. + +#Tests setting variants to invalid containers. +@pytest.mark.parametrize("container", [ + getInstanceContainer(container_type = "wrong container type"), + getInstanceContainer(container_type = "material"), #Existing, but still wrong type. + DefinitionContainer(container_id = "wrong class") +]) +def test_constrainVariantInvalid(container, extruder_stack): + with pytest.raises(InvalidContainerError): #Invalid container, should raise an error. + extruder_stack.variant = container + +#Test setting variants. +@pytest.mark.parametrize("container", [ + getInstanceContainer(container_type = "variant"), + InstanceContainerSubClass(container_type = "variant") +]) +def test_constrainVariantValid(container, extruder_stack): + extruder_stack.variant = container #Should not give an error. + +#Tests setting definitions to invalid containers. +@pytest.mark.parametrize("container", [ + getInstanceContainer(container_type = "wrong class"), + getInstanceContainer(container_type = "material"), #Existing, but still wrong class. +]) +def test_constrainVariantInvalid(container, extruder_stack): + with pytest.raises(InvalidContainerError): #Invalid container, should raise an error. + extruder_stack.definition = container + +#Test setting definitions. +@pytest.mark.parametrize("container", [ + DefinitionContainer(container_id = "DefinitionContainer"), + DefinitionContainerSubClass() +]) +def test_constrainDefinitionValid(container, extruder_stack): + extruder_stack.definition = container #Should not give an error. + +## Tests whether deserialising completes the missing containers with empty +# ones. +@pytest.mark.skip #The test currently fails because the definition container doesn't have a category, which is wrong but we don't have time to refactor that right now. +def test_deserializeCompletesEmptyContainers(extruder_stack: cura.Settings.ExtruderStack): + extruder_stack._containers = [DefinitionContainer(container_id = "definition")] #Set the internal state of this stack manually. + + with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize. + extruder_stack.deserialize("") + + assert len(extruder_stack.getContainers()) == len(cura.Settings.CuraContainerStack._ContainerIndexes.IndexTypeMap) #Needs a slot for every type. + for container_type_index in cura.Settings.CuraContainerStack._ContainerIndexes.IndexTypeMap: + if container_type_index == cura.Settings.CuraContainerStack._ContainerIndexes.Definition: #We're not checking the definition. + continue + assert extruder_stack.getContainer(container_type_index).getId() == "empty" #All others need to be empty. + +## Tests whether an instance container with the wrong type gets removed when +# deserialising. +def test_deserializeRemovesWrongInstanceContainer(extruder_stack): + extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Quality] = getInstanceContainer(container_type = "wrong type") + extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = DefinitionContainer(container_id = "some definition") + + with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize. + extruder_stack.deserialize("") + + assert extruder_stack.quality == extruder_stack._empty_instance_container #Replaced with empty. + +## Tests whether a container with the wrong class gets removed when +# deserialising. +def test_deserializeRemovesWrongContainerClass(extruder_stack): + extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Quality] = DefinitionContainer(container_id = "wrong class") + extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = DefinitionContainer(container_id = "some definition") + + with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize. + extruder_stack.deserialize("") + + assert extruder_stack.quality == extruder_stack._empty_instance_container #Replaced with empty. + +## Tests whether an instance container in the definition spot results in an +# error. +def test_deserializeWrongDefinitionClass(extruder_stack): + extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = getInstanceContainer(container_type = "definition") #Correct type but wrong class. + + with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize. + with pytest.raises(UM.Settings.ContainerStack.InvalidContainerStackError): #Must raise an error that there is no definition container. + extruder_stack.deserialize("") + +## Tests whether an instance container with the wrong type is moved into the +# correct slot by deserialising. +def test_deserializeMoveInstanceContainer(extruder_stack): + extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Quality] = getInstanceContainer(container_type = "material") #Not in the correct spot. + extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = DefinitionContainer(container_id = "some definition") + + with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize. + extruder_stack.deserialize("") + + assert extruder_stack.quality.getId() == "empty" + assert extruder_stack.material.getId() != "empty" + +## Tests whether a definition container in the wrong spot is moved into the +# correct spot by deserialising. +@pytest.mark.skip #The test currently fails because the definition container doesn't have a category, which is wrong but we don't have time to refactor that right now. +def test_deserializeMoveDefinitionContainer(extruder_stack): + extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Material] = DefinitionContainer(container_id = "some definition") #Not in the correct spot. + + with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize. + extruder_stack.deserialize("") + + assert extruder_stack.material.getId() == "empty" + assert extruder_stack.definition.getId() != "empty" + + UM.Settings.ContainerStack._containerRegistry = None + +## Tests whether getProperty properly applies the stack-like behaviour on its +# containers. +def test_getPropertyFallThrough(extruder_stack): + # ExtruderStack.setNextStack calls registerExtruder for backward compatibility, but we do not need a complete extruder manager + ExtruderManager._ExtruderManager__instance = unittest.mock.MagicMock() + + #A few instance container mocks to put in the stack. + mock_layer_heights = {} #For each container type, a mock container that defines layer height to something unique. + mock_no_settings = {} #For each container type, a mock container that has no settings at all. + container_indices = cura.Settings.CuraContainerStack._ContainerIndexes #Cache. + for type_id, type_name in container_indices.IndexTypeMap.items(): + container = unittest.mock.MagicMock() + container.getProperty = lambda key, property, type_id = type_id: type_id if (key == "layer_height" and property == "value") else None #Returns the container type ID as layer height, in order to identify it. + container.hasProperty = lambda key, property: key == "layer_height" + container.getMetaDataEntry = unittest.mock.MagicMock(return_value = type_name) + mock_layer_heights[type_id] = container + + container = unittest.mock.MagicMock() + container.getProperty = unittest.mock.MagicMock(return_value = None) #Has no settings at all. + container.hasProperty = unittest.mock.MagicMock(return_value = False) + container.getMetaDataEntry = unittest.mock.MagicMock(return_value = type_name) + mock_no_settings[type_id] = container + + extruder_stack.userChanges = mock_no_settings[container_indices.UserChanges] + extruder_stack.qualityChanges = mock_no_settings[container_indices.QualityChanges] + extruder_stack.quality = mock_no_settings[container_indices.Quality] + extruder_stack.material = mock_no_settings[container_indices.Material] + extruder_stack.variant = mock_no_settings[container_indices.Variant] + with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock): #To guard against the type checking. + extruder_stack.definition = mock_layer_heights[container_indices.Definition] #There's a layer height in here! + extruder_stack.setNextStack(unittest.mock.MagicMock()) + + assert extruder_stack.getProperty("layer_height", "value") == container_indices.Definition + extruder_stack.variant = mock_layer_heights[container_indices.Variant] + assert extruder_stack.getProperty("layer_height", "value") == container_indices.Variant + extruder_stack.material = mock_layer_heights[container_indices.Material] + assert extruder_stack.getProperty("layer_height", "value") == container_indices.Material + extruder_stack.quality = mock_layer_heights[container_indices.Quality] + assert extruder_stack.getProperty("layer_height", "value") == container_indices.Quality + extruder_stack.qualityChanges = mock_layer_heights[container_indices.QualityChanges] + assert extruder_stack.getProperty("layer_height", "value") == container_indices.QualityChanges + extruder_stack.userChanges = mock_layer_heights[container_indices.UserChanges] + assert extruder_stack.getProperty("layer_height", "value") == container_indices.UserChanges + +## Tests whether inserting a container is properly forbidden. +def test_insertContainer(extruder_stack): + with pytest.raises(InvalidOperationError): + extruder_stack.insertContainer(0, unittest.mock.MagicMock()) + +## Tests whether removing a container is properly forbidden. +def test_removeContainer(extruder_stack): + with pytest.raises(InvalidOperationError): + extruder_stack.removeContainer(unittest.mock.MagicMock()) + +## Tests setting definitions by specifying an ID of a definition that exists. +def test_setDefinitionByIdExists(extruder_stack, container_registry): + container_registry.return_value = DefinitionContainer(container_id = "some_definition") + extruder_stack.setDefinitionById("some_definition") + assert extruder_stack.definition.getId() == "some_definition" + +## Tests setting definitions by specifying an ID of a definition that doesn't +# exist. +def test_setDefinitionByIdDoesntExist(extruder_stack): + with pytest.raises(InvalidContainerError): + extruder_stack.setDefinitionById("some_definition") #Container registry is empty now. + +## Tests setting materials by specifying an ID of a material that exists. +def test_setMaterialByIdExists(extruder_stack, container_registry): + container_registry.return_value = getInstanceContainer(container_type = "material") + extruder_stack.setMaterialById("InstanceContainer") + assert extruder_stack.material.getId() == "InstanceContainer" + +## Tests setting materials by specifying an ID of a material that doesn't +# exist. +def test_setMaterialByIdDoesntExist(extruder_stack): + with pytest.raises(InvalidContainerError): + extruder_stack.setMaterialById("some_material") #Container registry is empty now. + +## Tests setting properties directly on the extruder stack. +@pytest.mark.parametrize("key, property, value", [ + ("layer_height", "value", 0.1337), + ("foo", "value", 100), + ("support_enabled", "value", True), + ("layer_height", "default_value", 0.1337), + ("layer_height", "is_bright_pink", "of course") +]) +def test_setPropertyUser(key, property, value, extruder_stack): + user_changes = unittest.mock.MagicMock() + user_changes.getMetaDataEntry = unittest.mock.MagicMock(return_value = "user") + extruder_stack.userChanges = user_changes + + extruder_stack.setProperty(key, property, value) #The actual test. + + extruder_stack.userChanges.setProperty.assert_called_once_with(key, property, value) #Make sure that the user container gets a setProperty call. + +## Tests setting properties on specific containers on the global stack. +@pytest.mark.parametrize("target_container, stack_variable", [ + ("user", "userChanges"), + ("quality_changes", "qualityChanges"), + ("quality", "quality"), + ("material", "material"), + ("variant", "variant") +]) +def test_setPropertyOtherContainers(target_container, stack_variable, extruder_stack): + #Other parameters that don't need to be varied. + key = "layer_height" + property = "value" + value = 0.1337 + #A mock container in the right spot. + container = unittest.mock.MagicMock() + container.getMetaDataEntry = unittest.mock.MagicMock(return_value = target_container) + setattr(extruder_stack, stack_variable, container) #For instance, set global_stack.qualityChanges = container. + + extruder_stack.setProperty(key, property, value, target_container = target_container) #The actual test. + + getattr(extruder_stack, stack_variable).setProperty.assert_called_once_with(key, property, value) #Make sure that the proper container gets a setProperty call. + +## Tests setting qualities by specifying an ID of a quality that exists. +def test_setQualityByIdExists(extruder_stack, container_registry): + container_registry.return_value = getInstanceContainer(container_type = "quality") + extruder_stack.setQualityById("InstanceContainer") + assert extruder_stack.quality.getId() == "InstanceContainer" + +## Tests setting qualities by specifying an ID of a quality that doesn't exist. +def test_setQualityByIdDoesntExist(extruder_stack): + with pytest.raises(InvalidContainerError): + extruder_stack.setQualityById("some_quality") #Container registry is empty now. + +## Tests setting quality changes by specifying an ID of a quality change that +# exists. +def test_setQualityChangesByIdExists(extruder_stack, container_registry): + container_registry.return_value = getInstanceContainer(container_type = "quality_changes") + extruder_stack.setQualityChangesById("InstanceContainer") + assert extruder_stack.qualityChanges.getId() == "InstanceContainer" + +## Tests setting quality changes by specifying an ID of a quality change that +# doesn't exist. +def test_setQualityChangesByIdDoesntExist(extruder_stack): + with pytest.raises(InvalidContainerError): + extruder_stack.setQualityChangesById("some_quality_changes") #Container registry is empty now. + +## Tests setting variants by specifying an ID of a variant that exists. +def test_setVariantByIdExists(extruder_stack, container_registry): + container_registry.return_value = getInstanceContainer(container_type = "variant") + extruder_stack.setVariantById("InstanceContainer") + assert extruder_stack.variant.getId() == "InstanceContainer" + +## Tests setting variants by specifying an ID of a variant that doesn't exist. +def test_setVariantByIdDoesntExist(extruder_stack): + with pytest.raises(InvalidContainerError): + extruder_stack.setVariantById("some_variant") #Container registry is empty now. diff --git a/tests/Settings/TestGlobalStack.py b/tests/Settings/TestGlobalStack.py new file mode 100755 index 0000000000..1eb3c43746 --- /dev/null +++ b/tests/Settings/TestGlobalStack.py @@ -0,0 +1,559 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + +import pytest #This module contains unit tests. +import unittest.mock #To monkeypatch some mocks in place of dependencies. + +import cura.Settings.GlobalStack #The module we're testing. +import cura.Settings.CuraContainerStack #To get the list of container types. +from cura.Settings.Exceptions import TooManyExtrudersError, InvalidContainerError, InvalidOperationError #To test raising these errors. +from UM.Settings.DefinitionContainer import DefinitionContainer #To test against the class DefinitionContainer. +from UM.Settings.InstanceContainer import InstanceContainer #To test against the class InstanceContainer. +from UM.Settings.SettingInstance import InstanceState +import UM.Settings.ContainerRegistry +import UM.Settings.ContainerStack + +## Fake container registry that always provides all containers you ask of. +@pytest.yield_fixture() +def container_registry(): + registry = unittest.mock.MagicMock() + registry.return_value = unittest.mock.NonCallableMagicMock() + registry.findInstanceContainers = lambda *args, registry = registry, **kwargs: [registry.return_value] + registry.findDefinitionContainers = lambda *args, registry = registry, **kwargs: [registry.return_value] + + UM.Settings.ContainerRegistry.ContainerRegistry._ContainerRegistry__instance = registry + UM.Settings.ContainerStack._containerRegistry = registry + + yield registry + + UM.Settings.ContainerRegistry.ContainerRegistry._ContainerRegistry__instance = None + UM.Settings.ContainerStack._containerRegistry = None + +#An empty global stack to test with. +@pytest.fixture() +def global_stack() -> cura.Settings.GlobalStack.GlobalStack: + return cura.Settings.GlobalStack.GlobalStack("TestStack") + +## Gets an instance container with a specified container type. +# +# \param container_type The type metadata for the instance container. +# \return An instance container instance. +def getInstanceContainer(container_type) -> InstanceContainer: + container = InstanceContainer(container_id = "InstanceContainer") + container.addMetaDataEntry("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.addMetaDataEntry("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() + mock_definition.getProperty = lambda key, property: 2 if key == "machine_extruder_count" and property == "value" else None + + with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock): + global_stack.definition = mock_definition + + assert len(global_stack.extruders) == 0 + first_extruder = unittest.mock.MagicMock() + with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock): + global_stack.addExtruder(first_extruder) + assert len(global_stack.extruders) == 1 + assert global_stack.extruders[0] == first_extruder + second_extruder = unittest.mock.MagicMock() + with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock): + global_stack.addExtruder(second_extruder) + assert len(global_stack.extruders) == 2 + assert global_stack.extruders[1] == second_extruder + # Disabled for now for Custom FDM Printer + # with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock): + # with pytest.raises(TooManyExtrudersError): #Should be limited to 2 extruders because of machine_extruder_count. + # 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"), + getInstanceContainer(container_type = "material"), #Existing, but still wrong type. + DefinitionContainer(container_id = "wrong class") +]) +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"), + InstanceContainerSubClass(container_type = "user") +]) +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"), + getInstanceContainer(container_type = "material"), #Existing, but still wrong type. + DefinitionContainer(container_id = "wrong class") +]) +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"), + InstanceContainerSubClass(container_type = "quality_changes") +]) +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"), + getInstanceContainer(container_type = "material"), #Existing, but still wrong type. + DefinitionContainer(container_id = "wrong class") +]) +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"), + InstanceContainerSubClass(container_type = "quality") +]) +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"), + getInstanceContainer(container_type = "quality"), #Existing, but still wrong type. + DefinitionContainer(container_id = "wrong class") +]) +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"), + InstanceContainerSubClass(container_type = "material") +]) +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"), + getInstanceContainer(container_type = "material"), #Existing, but still wrong type. + DefinitionContainer(container_id = "wrong class") +]) +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"), + InstanceContainerSubClass(container_type = "variant") +]) +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"), + getInstanceContainer(container_type = "material"), #Existing, but still wrong type. + DefinitionContainer(container_id = "wrong class") +]) +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"), + InstanceContainerSubClass(container_type = "definition_changes") +]) +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"), + getInstanceContainer(container_type = "material"), #Existing, but still wrong class. +]) +def test_constrainVariantInvalid(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"), + DefinitionContainerSubClass() +]) +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. +@pytest.mark.skip #The test currently fails because the definition container doesn't have a category, which is wrong but we don't have time to refactor that right now. +def test_deserializeCompletesEmptyContainers(global_stack: cura.Settings.GlobalStack): + global_stack._containers = [DefinitionContainer(container_id = "definition")] #Set the internal state of this stack manually. + + with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize. + global_stack.deserialize("") + + assert len(global_stack.getContainers()) == len(cura.Settings.CuraContainerStack._ContainerIndexes.IndexTypeMap) #Needs a slot for every type. + for container_type_index in cura.Settings.CuraContainerStack._ContainerIndexes.IndexTypeMap: + if container_type_index == cura.Settings.CuraContainerStack._ContainerIndexes.Definition: #We're not checking the definition. + continue + assert global_stack.getContainer(container_type_index).getId() == "empty" #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") + global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = DefinitionContainer(container_id = "some definition") + + with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize. + global_stack.deserialize("") + + 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") + global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = DefinitionContainer(container_id = "some definition") + + with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize. + global_stack.deserialize("") + + 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. + + with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize. + 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. + global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = DefinitionContainer(container_id = "some definition") + + with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize. + global_stack.deserialize("") + + assert global_stack.quality.getId() == "empty" + assert global_stack.material.getId() != "empty" + +## Tests whether a definition container in the wrong spot is moved into the +# correct spot by deserialising. +@pytest.mark.skip #The test currently fails because the definition container doesn't have a category, which is wrong but we don't have time to refactor that right now. +def test_deserializeMoveDefinitionContainer(global_stack): + global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Material] = DefinitionContainer(container_id = "some definition") #Not in the correct spot. + + with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize. + global_stack.deserialize("") + + assert global_stack.material.getId() == "empty" + assert global_stack.definition.getId() != "empty" + + UM.Settings.ContainerStack._containerRegistry = None + +## 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. + mock_layer_heights = {} #For each container type, a mock container that defines layer height to something unique. + mock_no_settings = {} #For each container type, a mock container that has no settings at all. + container_indexes = cura.Settings.CuraContainerStack._ContainerIndexes #Cache. + for type_id, type_name in container_indexes.IndexTypeMap.items(): + container = unittest.mock.MagicMock() + container.getProperty = lambda key, property, type_id = type_id: type_id if (key == "layer_height" and property == "value") else None #Returns the container type ID as layer height, in order to identify it. + container.hasProperty = lambda key, property: key == "layer_height" + container.getMetaDataEntry = unittest.mock.MagicMock(return_value = type_name) + mock_layer_heights[type_id] = container + + container = unittest.mock.MagicMock() + container.getProperty = unittest.mock.MagicMock(return_value = None) #Has no settings at all. + container.hasProperty = unittest.mock.MagicMock(return_value = False) + container.getMetaDataEntry = unittest.mock.MagicMock(return_value = type_name) + mock_no_settings[type_id] = container + + global_stack.userChanges = mock_no_settings[container_indexes.UserChanges] + global_stack.qualityChanges = mock_no_settings[container_indexes.QualityChanges] + global_stack.quality = mock_no_settings[container_indexes.Quality] + global_stack.material = mock_no_settings[container_indexes.Material] + global_stack.variant = mock_no_settings[container_indexes.Variant] + global_stack.definitionChanges = mock_no_settings[container_indexes.DefinitionChanges] + with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock): #To guard against the type checking. + global_stack.definition = mock_layer_heights[container_indexes.Definition] #There's a layer height in here! + + assert global_stack.getProperty("layer_height", "value") == container_indexes.Definition + global_stack.definitionChanges = mock_layer_heights[container_indexes.DefinitionChanges] + assert global_stack.getProperty("layer_height", "value") == container_indexes.DefinitionChanges + global_stack.variant = mock_layer_heights[container_indexes.Variant] + assert global_stack.getProperty("layer_height", "value") == container_indexes.Variant + global_stack.material = mock_layer_heights[container_indexes.Material] + assert global_stack.getProperty("layer_height", "value") == container_indexes.Material + global_stack.quality = mock_layer_heights[container_indexes.Quality] + assert global_stack.getProperty("layer_height", "value") == container_indexes.Quality + global_stack.qualityChanges = mock_layer_heights[container_indexes.QualityChanges] + assert global_stack.getProperty("layer_height", "value") == container_indexes.QualityChanges + 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. + value.getProperty = lambda key, property: 10 if (key == "material_bed_temperature" and property == "value") else None + + with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock): #To guard against the type checking. + 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. + resolve_and_value.getProperty = lambda key, property: (7.5 if property == "resolve" else 5) if (key == "material_bed_temperature" and property in ("resolve", "value")) else None #7.5 resolve, 5 value. + + with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock): #To guard against the type checking. + 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 + instance_containers = {} + for container_type in container_indices.IndexTypeMap: + instance_containers[container_type] = unittest.mock.MagicMock() #Sets the resolve and value for bed temperature. + instance_containers[container_type].getProperty = lambda key, property: (7.5 if property == "resolve" else (InstanceState.User if property == "state" else 5)) if (key == "material_bed_temperature") else None #7.5 resolve, 5 value. + instance_containers[container_type].getMetaDataEntry = unittest.mock.MagicMock(return_value = container_indices.IndexTypeMap[container_type]) #Make queries for the type return the desired type. + instance_containers[container_indices.Definition].getProperty = lambda key, property: 10 if (key == "material_bed_temperature" and property == "value") else None #Definition only has value. + with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock): #To guard against the type checking. + global_stack.definition = instance_containers[container_indices.Definition] #Stack must have a definition. + + #For all instance container slots, the value reigns over resolve. + global_stack.definitionChanges = instance_containers[container_indices.DefinitionChanges] + assert global_stack.getProperty("material_bed_temperature", "value") == 5 + global_stack.variant = instance_containers[container_indices.Variant] + assert global_stack.getProperty("material_bed_temperature", "value") == 5 + global_stack.material = instance_containers[container_indices.Material] + assert global_stack.getProperty("material_bed_temperature", "value") == 5 + global_stack.quality = instance_containers[container_indices.Quality] + assert global_stack.getProperty("material_bed_temperature", "value") == 5 + global_stack.qualityChanges = instance_containers[container_indices.QualityChanges] + assert global_stack.getProperty("material_bed_temperature", "value") == 5 + 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. + value.getProperty = lambda key, property: (10 if property == "value" else InstanceState.User) if key == "material_bed_temperature" else None + value.getMetaDataEntry = unittest.mock.MagicMock(return_value = "quality") + resolve = unittest.mock.MagicMock() #Sets just the resolve. + resolve.getProperty = lambda key, property: 7.5 if (key == "material_bed_temperature" and property == "resolve") else None + + with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock): #To guard against the type checking. + global_stack.definition = resolve + global_stack.quality = value + + 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() + container.getMetaDataEntry = unittest.mock.MagicMock(return_value = "user") + container.hasProperty = lambda key, property: key == "layer_height" #Only have the layer_height property set. + global_stack.userChanges = container + + assert global_stack.hasUserValue("layer_height") + 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() + container.getMetaDataEntry = unittest.mock.MagicMock(return_value = "quality_changes") + container.hasProperty = lambda key, property: key == "layer_height" #Only have the layer_height property set. + global_stack.qualityChanges = container + + assert global_stack.hasUserValue("layer_height") + 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() + container.getMetaDataEntry = unittest.mock.MagicMock(return_value = "quality") + container.hasProperty = lambda key, property: key == "layer_height" #Only have the layer_height property set. + global_stack.quality = container + + 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 setting definitions by specifying an ID of a definition that exists. +def test_setDefinitionByIdExists(global_stack, container_registry): + container_registry.return_value = DefinitionContainer(container_id = "some_definition") + global_stack.setDefinitionById("some_definition") + assert global_stack.definition.getId() == "some_definition" + +## Tests setting definitions by specifying an ID of a definition that doesn't +# exist. +def test_setDefinitionByIdDoesntExist(global_stack): + with pytest.raises(InvalidContainerError): + global_stack.setDefinitionById("some_definition") #Container registry is empty now. + +## Tests setting definition changes by specifying an ID of a container that +# exists. +def test_setDefinitionChangesByIdExists(global_stack, container_registry): + container_registry.return_value = getInstanceContainer(container_type = "definition_changes") + global_stack.setDefinitionChangesById("InstanceContainer") + assert global_stack.definitionChanges.getId() == "InstanceContainer" + +## Tests setting definition changes by specifying an ID of a container that +# doesn't exist. +def test_setDefinitionChangesByIdDoesntExist(global_stack): + with pytest.raises(InvalidContainerError): + global_stack.setDefinitionChangesById("some_definition_changes") #Container registry is empty now. + +## Tests setting materials by specifying an ID of a material that exists. +def test_setMaterialByIdExists(global_stack, container_registry): + container_registry.return_value = getInstanceContainer(container_type = "material") + global_stack.setMaterialById("InstanceContainer") + assert global_stack.material.getId() == "InstanceContainer" + +## Tests setting materials by specifying an ID of a material that doesn't +# exist. +def test_setMaterialByIdDoesntExist(global_stack): + with pytest.raises(InvalidContainerError): + global_stack.setMaterialById("some_material") #Container registry is empty now. + +## 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), + ("foo", "value", 100), + ("support_enabled", "value", True), + ("layer_height", "default_value", 0.1337), + ("layer_height", "is_bright_pink", "of course") +]) +def test_setPropertyUser(key, property, value, global_stack): + user_changes = unittest.mock.MagicMock() + user_changes.getMetaDataEntry = unittest.mock.MagicMock(return_value = "user") + global_stack.userChanges = user_changes + + global_stack.setProperty(key, property, value) #The actual test. + + global_stack.userChanges.setProperty.assert_called_once_with(key, property, value) #Make sure that the user container gets a setProperty call. + +## Tests setting properties on specific containers on the global stack. +@pytest.mark.parametrize("target_container, stack_variable", [ + ("user", "userChanges"), + ("quality_changes", "qualityChanges"), + ("quality", "quality"), + ("material", "material"), + ("variant", "variant"), + ("definition_changes", "definitionChanges") +]) +def test_setPropertyOtherContainers(target_container, stack_variable, global_stack): + #Other parameters that don't need to be varied. + key = "layer_height" + property = "value" + value = 0.1337 + #A mock container in the right spot. + container = unittest.mock.MagicMock() + container.getMetaDataEntry = unittest.mock.MagicMock(return_value = target_container) + setattr(global_stack, stack_variable, container) #For instance, set global_stack.qualityChanges = container. + + global_stack.setProperty(key, property, value, target_container = target_container) #The actual test. + + getattr(global_stack, stack_variable).setProperty.assert_called_once_with(key, property, value) #Make sure that the proper container gets a setProperty call. + +## Tests setting qualities by specifying an ID of a quality that exists. +def test_setQualityByIdExists(global_stack, container_registry): + container_registry.return_value = getInstanceContainer(container_type = "quality") + global_stack.setQualityById("InstanceContainer") + assert global_stack.quality.getId() == "InstanceContainer" + +## Tests setting qualities by specifying an ID of a quality that doesn't exist. +def test_setQualityByIdDoesntExist(global_stack): + with pytest.raises(InvalidContainerError): + global_stack.setQualityById("some_quality") #Container registry is empty now. + +## Tests setting quality changes by specifying an ID of a quality change that +# exists. +def test_setQualityChangesByIdExists(global_stack, container_registry): + container_registry.return_value = getInstanceContainer(container_type = "quality_changes") + global_stack.setQualityChangesById("InstanceContainer") + assert global_stack.qualityChanges.getId() == "InstanceContainer" + +## Tests setting quality changes by specifying an ID of a quality change that +# doesn't exist. +def test_setQualityChangesByIdDoesntExist(global_stack): + with pytest.raises(InvalidContainerError): + global_stack.setQualityChangesById("some_quality_changes") #Container registry is empty now. + +## Tests setting variants by specifying an ID of a variant that exists. +def test_setVariantByIdExists(global_stack, container_registry): + container_registry.return_value = getInstanceContainer(container_type = "variant") + global_stack.setVariantById("InstanceContainer") + assert global_stack.variant.getId() == "InstanceContainer" + +## Tests setting variants by specifying an ID of a variant that doesn't exist. +def test_setVariantByIdDoesntExist(global_stack): + with pytest.raises(InvalidContainerError): + global_stack.setVariantById("some_variant") #Container registry is empty now. + +## Smoke test for findDefaultVariant +def test_smoke_findDefaultVariant(global_stack): + global_stack.findDefaultVariant() + +## Smoke test for findDefaultMaterial +def test_smoke_findDefaultMaterial(global_stack): + global_stack.findDefaultMaterial() + +## Smoke test for findDefaultQuality +def test_smoke_findDefaultQuality(global_stack): + global_stack.findDefaultQuality() diff --git a/tests/Settings/stacks/Complete.extruder.cfg b/tests/Settings/stacks/Complete.extruder.cfg new file mode 100644 index 0000000000..789c0978f3 --- /dev/null +++ b/tests/Settings/stacks/Complete.extruder.cfg @@ -0,0 +1,12 @@ +[general] +version = 3 +name = Complete +id = Complete + +[containers] +0 = some_user_changes +1 = some_quality_changes +2 = some_quality +3 = some_material +4 = some_variant +5 = some_definition diff --git a/tests/Settings/stacks/Complete.global.cfg b/tests/Settings/stacks/Complete.global.cfg new file mode 100644 index 0000000000..f7f613991a --- /dev/null +++ b/tests/Settings/stacks/Complete.global.cfg @@ -0,0 +1,13 @@ +[general] +version = 3 +name = Complete +id = Complete + +[containers] +0 = some_user_changes +1 = some_quality_changes +2 = some_quality +3 = some_material +4 = some_variant +5 = some_definition_changes +6 = some_definition diff --git a/tests/Settings/stacks/ExtruderLegacy.stack.cfg b/tests/Settings/stacks/ExtruderLegacy.stack.cfg new file mode 100644 index 0000000000..4a6c419e40 --- /dev/null +++ b/tests/Settings/stacks/ExtruderLegacy.stack.cfg @@ -0,0 +1,11 @@ +[general] +version = 3 +name = Legacy Extruder Stack +id = ExtruderLegacy + +[metadata] +type = extruder_train + +[containers] +3 = some_instance +5 = some_definition diff --git a/tests/Settings/stacks/Global.global.cfg b/tests/Settings/stacks/Global.global.cfg new file mode 100644 index 0000000000..9034c1d0d0 --- /dev/null +++ b/tests/Settings/stacks/Global.global.cfg @@ -0,0 +1,8 @@ +[general] +version = 3 +name = Global +id = Global + +[containers] +3 = some_instance +6 = some_definition diff --git a/tests/Settings/stacks/Global.stack.cfg b/tests/Settings/stacks/Global.stack.cfg new file mode 100644 index 0000000000..aa1693d878 --- /dev/null +++ b/tests/Settings/stacks/Global.stack.cfg @@ -0,0 +1,11 @@ +[general] +version = 3 +name = Global +id = Global + +[metadata] +type = machine + +[containers] +3 = some_instance +6 = some_definition diff --git a/tests/Settings/stacks/Left.extruder.cfg b/tests/Settings/stacks/Left.extruder.cfg new file mode 100644 index 0000000000..8ba45d6754 --- /dev/null +++ b/tests/Settings/stacks/Left.extruder.cfg @@ -0,0 +1,8 @@ +[general] +version = 3 +name = Left +id = Left + +[containers] +3 = some_instance +5 = some_definition diff --git a/tests/Settings/stacks/MachineLegacy.stack.cfg b/tests/Settings/stacks/MachineLegacy.stack.cfg new file mode 100644 index 0000000000..147d63c596 --- /dev/null +++ b/tests/Settings/stacks/MachineLegacy.stack.cfg @@ -0,0 +1,11 @@ +[general] +version = 3 +name = Legacy Global Stack +id = MachineLegacy + +[metadata] +type = machine + +[containers] +3 = some_instance +6 = some_definition \ No newline at end of file diff --git a/tests/Settings/stacks/OnlyDefinition.extruder.cfg b/tests/Settings/stacks/OnlyDefinition.extruder.cfg new file mode 100644 index 0000000000..e58512b27f --- /dev/null +++ b/tests/Settings/stacks/OnlyDefinition.extruder.cfg @@ -0,0 +1,7 @@ +[general] +version = 3 +name = Only Definition +id = OnlyDefinition + +[containers] +5 = some_definition diff --git a/tests/Settings/stacks/OnlyDefinition.global.cfg b/tests/Settings/stacks/OnlyDefinition.global.cfg new file mode 100644 index 0000000000..9534353ed5 --- /dev/null +++ b/tests/Settings/stacks/OnlyDefinition.global.cfg @@ -0,0 +1,7 @@ +[general] +version = 3 +name = Only Definition +id = OnlyDefinition + +[containers] +6 = some_definition diff --git a/tests/Settings/stacks/OnlyDefinitionChanges.global.cfg b/tests/Settings/stacks/OnlyDefinitionChanges.global.cfg new file mode 100644 index 0000000000..39e2105b7d --- /dev/null +++ b/tests/Settings/stacks/OnlyDefinitionChanges.global.cfg @@ -0,0 +1,8 @@ +[general] +version = 3 +name = Only Definition Changes +id = OnlyDefinitionChanges + +[containers] +5 = some_instance +6 = some_definition diff --git a/tests/Settings/stacks/OnlyMaterial.extruder.cfg b/tests/Settings/stacks/OnlyMaterial.extruder.cfg new file mode 100644 index 0000000000..49a9d12389 --- /dev/null +++ b/tests/Settings/stacks/OnlyMaterial.extruder.cfg @@ -0,0 +1,8 @@ +[general] +version = 3 +name = Only Material +id = OnlyMaterial + +[containers] +3 = some_instance +5 = some_definition diff --git a/tests/Settings/stacks/OnlyMaterial.global.cfg b/tests/Settings/stacks/OnlyMaterial.global.cfg new file mode 100644 index 0000000000..715651a9b9 --- /dev/null +++ b/tests/Settings/stacks/OnlyMaterial.global.cfg @@ -0,0 +1,8 @@ +[general] +version = 3 +name = Only Material +id = OnlyMaterial + +[containers] +3 = some_instance +6 = some_definition diff --git a/tests/Settings/stacks/OnlyQuality.extruder.cfg b/tests/Settings/stacks/OnlyQuality.extruder.cfg new file mode 100644 index 0000000000..aaf7fb30c5 --- /dev/null +++ b/tests/Settings/stacks/OnlyQuality.extruder.cfg @@ -0,0 +1,8 @@ +[general] +version = 3 +name = Only Quality +id = OnlyQuality + +[containers] +2 = some_instance +5 = some_definition diff --git a/tests/Settings/stacks/OnlyQuality.global.cfg b/tests/Settings/stacks/OnlyQuality.global.cfg new file mode 100644 index 0000000000..f07a35666e --- /dev/null +++ b/tests/Settings/stacks/OnlyQuality.global.cfg @@ -0,0 +1,8 @@ +[general] +version = 3 +name = Only Quality +id = OnlyQuality + +[containers] +2 = some_instance +6 = some_definition diff --git a/tests/Settings/stacks/OnlyQualityChanges.extruder.cfg b/tests/Settings/stacks/OnlyQualityChanges.extruder.cfg new file mode 100644 index 0000000000..653bad840c --- /dev/null +++ b/tests/Settings/stacks/OnlyQualityChanges.extruder.cfg @@ -0,0 +1,8 @@ +[general] +version = 3 +name = Only Quality Changes +id = OnlyQualityChanges + +[containers] +1 = some_instance +5 = some_definition diff --git a/tests/Settings/stacks/OnlyQualityChanges.global.cfg b/tests/Settings/stacks/OnlyQualityChanges.global.cfg new file mode 100644 index 0000000000..17d279377a --- /dev/null +++ b/tests/Settings/stacks/OnlyQualityChanges.global.cfg @@ -0,0 +1,8 @@ +[general] +version = 3 +name = Only Quality Changes +id = OnlyQualityChanges + +[containers] +1 = some_instance +6 = some_definition diff --git a/tests/Settings/stacks/OnlyUser.extruder.cfg b/tests/Settings/stacks/OnlyUser.extruder.cfg new file mode 100644 index 0000000000..abf812a859 --- /dev/null +++ b/tests/Settings/stacks/OnlyUser.extruder.cfg @@ -0,0 +1,8 @@ +[general] +version = 3 +name = Only User +id = OnlyUser + +[containers] +0 = some_instance +5 = some_definition diff --git a/tests/Settings/stacks/OnlyUser.global.cfg b/tests/Settings/stacks/OnlyUser.global.cfg new file mode 100644 index 0000000000..31371d2c51 --- /dev/null +++ b/tests/Settings/stacks/OnlyUser.global.cfg @@ -0,0 +1,8 @@ +[general] +version = 3 +name = Only User +id = OnlyUser + +[containers] +0 = some_instance +6 = some_definition diff --git a/tests/Settings/stacks/OnlyVariant.extruder.cfg b/tests/Settings/stacks/OnlyVariant.extruder.cfg new file mode 100644 index 0000000000..a31997a6fd --- /dev/null +++ b/tests/Settings/stacks/OnlyVariant.extruder.cfg @@ -0,0 +1,8 @@ +[general] +version = 3 +name = Only Variant +id = OnlyVariant + +[containers] +4 = some_instance +5 = some_definition diff --git a/tests/Settings/stacks/OnlyVariant.global.cfg b/tests/Settings/stacks/OnlyVariant.global.cfg new file mode 100644 index 0000000000..158d533ac8 --- /dev/null +++ b/tests/Settings/stacks/OnlyVariant.global.cfg @@ -0,0 +1,8 @@ +[general] +version = 3 +name = Only Variant +id = OnlyVariant + +[containers] +4 = some_instance +6 = some_definition